DynamicActiveQuery.php
public function createCommand($db = null)
    {
        /** @var DynamicActiveRecord $modelClass */
        $modelClass = $this->modelClass;
        if ($db === null) {
            $db = $modelClass::getDb();
        }

        if ($this->sql === null) {
            list ($sql, $params) = $db->getQueryBuilder()->build($this);
        } else {
            $sql = $this->sql;
            $params = $this->params;
        }

        $dynamicColumn = $modelClass::dynamicColumn();
        $callback = function ($matches) use (&$params, $dynamicColumn) {
            $type = !empty($matches[3]) ? $matches[3] : 'CHAR';
            $sql = $dynamicColumn;
            $parts = explode('.', $matches[2]);
            $lastPart = array_pop($parts);
            foreach ($parts as $column) {
                $placeholder = DynamicActiveRecord::placeholder();
                $params[$placeholder] = $column;
                $sql = "COLUMN_GET($sql, $placeholder AS BINARY)";
            }
            $placeholder = DynamicActiveRecord::placeholder();
            $params[$placeholder] = $lastPart;
            $sql = "COLUMN_GET($sql, $placeholder AS $type)";

            return $sql;
        };

        $pattern = <<<'REGEXP'
            % (`?) \(! \s*?
                ( [a-z_\x7f-\xff][a-z0-9_\x7f-\xff]* (?: \. [^.|\s]+)* )
                (?:  \| (binary (?:\(\d+\))? | char (?:\(\d+\))? | time (?:\(\d+\))? | datetime (?:\(\d+\))? | date
                        | decimal (?:\(\d\d?(?:,\d\d?)?\))?  | double (?:\(\d\d?,\d\d?\))?
                        | int(eger)? | (?:un)? signed (?:\s+int(eger)?)?)  )?
            \s*? !\) \1 %ix
REGEXP;
        $sql = preg_replace_callback($pattern, $callback, $sql);

        return $db->createCommand($sql, $params);
    }
public function createCommand($db = null)
    {
        /** @var DynamicActiveRecord $modelClass */
        $modelClass = $this->modelClass;
        if ($db === null) {
            $db = $modelClass::getDb();
        }

        if ($this->sql === null) {
            list ($sql, $params) = $db->getQueryBuilder()->build($this);
        } else {
            $sql = $this->sql;
            $params = $this->params;
        }

        $dynamicColumn = $modelClass::dynamicColumn();
        $callback = function ($matches) use (&$params, $dynamicColumn) {
            $type = !empty($matches[3]) ? $matches[3] : 'CHAR';
            $sql = $dynamicColumn;
            $parts = explode('.', $matches[2]);
            $lastPart = array_pop($parts);
            foreach ($parts as $column) {
                $placeholder = DynamicActiveRecord::placeholder();
                $params[$placeholder] = $column;
                $sql = "COLUMN_GET($sql, $placeholder AS BINARY)";
            }
            $placeholder = DynamicActiveRecord::placeholder();
            $params[$placeholder] = $lastPart;
            $sql = "COLUMN_GET($sql, $placeholder AS $type)";

            return $sql;
        };

        $pattern = <<<'REGEXP'
            % (`?) \(! \s*?
                ( [a-z_\x7f-\xff][a-z0-9_\x7f-\xff]* (?: \. [^.|\s]+)* )
                (?:  \| (binary (?:\(\d+\))? | char (?:\(\d+\))? | time (?:\(\d+\))? | datetime (?:\(\d+\))? | date
                        | decimal (?:\(\d\d?(?:,\d\d?)?\))?  | double (?:\(\d\d?,\d\d?\))?
                        | int(eger)? | (?:un)? signed (?:\s+int(eger)?)?)  )?
            \s*? !\) \1 %ix
REGEXP;
        $sql = preg_replace_callback($pattern, $callback, $sql);

        return $db->createCommand($sql, $params);
    }
public function createCommand ( $ db = null ) { /** @var DynamicActiveRecord $modelClass */ $ modelClass = $ this -> modelClass ; if ( $ db === null ) { $ db = $ modelClass :: getDb ( ) ; } if ( $ this -> sql === null ) { list ( $ sql , $ params ) = $ db -> getQueryBuilder ( ) -> build ( $ this ) ; } else { $ sql = $ this -> sql ; $ params = $ this -> params ; } $ dynamicColumn = $ modelClass :: dynamicColumn ( ) ; $ callback = function ( $ matches ) use ( & $ params , $ dynamicColumn ) { $ type = ! empty ( $ matches [ 3 ] ) ? $ matches [ 3 ] : 'CHAR' ; $ sql = $ dynamicColumn ; $ parts = explode ( '.' , $ matches [ 2 ] ) ; $ lastPart = array_pop ( $ parts ) ; foreach ( $ parts as $ column ) { $ placeholder = DynamicActiveRecord :: placeholder ( ) ; $ params [ $ placeholder ] = $ column ; $ sql = "COLUMN_GET($sql, $placeholder AS BINARY)" ; } $ placeholder = DynamicActiveRecord :: placeholder ( ) ; $ params [ $ placeholder ] = $ lastPart ; $ sql = "COLUMN_GET($sql, $placeholder AS $type)" ; return $ sql ; } ; $ pattern =  <<<'REGEXP'
            % (`?) \(! \s*?
                ( [a-z_\x7f-\xff][a-z0-9_\x7f-\xff]* (?: \. [^.|\s]+)* )
                (?:  \| (binary (?:\(\d+\))? | char (?:\(\d+\))? | time (?:\(\d+\))? | datetime (?:\(\d+\))? | date
                        | decimal (?:\(\d\d?(?:,\d\d?)?\))?  | double (?:\(\d\d?,\d\d?\))?
                        | int(eger)? | (?:un)? signed (?:\s+int(eger)?)?)  )?
            \s*? !\) \1 %ix
REGEXP ; $ sql = preg_replace_callback ( $ pattern , $ callback , $ sql ) ; return $ db -> createCommand ( $ sql , $ params ) ; }
Generate DB command from ActiveQuery with Maria-specific SQL for dynamic columns.

User of DynamicActiveQuery should not normally need to use this method.

#### History

This implementation is the best I could manage. A dynamic attribute name
can appear anywhere that a schema attribute name could appear (select, join, where, ...).
It needs to be converted to the Maria SQL using COLUMN_CREATE('name', value, …)
for accessing dynamic columns.
Because SQL is statically-typed and there is no schema to refer to for dynamic
attributes, the accessor SQL must specify the the dyn-col's type, e.g.

```sql
WHERE COLUMN_GET(details, 'color' AS CHAR) = 'black'
```

In which details is the blob column containing all the dynamic columns, 'color' is the
name of a dynamic column that may or may not appear in any given table record, and
CHAR means the value should be cast to CHAR before it is compared with 'black'.
`COLUMN_GET(details, 'color' AS CHAR)` is the "accessor SQL".

So I faced two problems:

1. How to identify a dynamic attribute name in an ActiveQuery?
2. How to choose the type to which it should be cast in the SQL?

The design prociple of DynamicAR is "an attribute that isn't an instance variable,
a column and doesn't have a magic get-/setter is assumed to be a dynamic attribute".
So, in order to infer from the properties of an AQ instance the attribute names
that need to be converted to dynamic column accessor SQL, I need to go through
the AQ to identify
all the column names and remove those in the schema. But I don't know how to
identify column names in an AQ instance. Even if I did, there's problem 2.

The only way I can imagine to infer datatype from an AQ instance is to look
at the context. If the attribute is compared with a bound parameter, that's a clue.
If it is being used in an SQL function, e.g. CONCAT(), or being compared with a
schema column, that suggests something. But if it is on its own in a SELECT then I am
stuck. Also stuck if it is compared with another dynamic attribute. This seems
fundamentally intractible.

So I decided that the user needs to help DynamicActiveQuery by distinguishing the names
of dynamic attributes and by explicitly specifying the type. The format for this:

(!name|type!)

Omitting type implies the default type: CHAR. Children of dynamic attributes, i.e.
array elements, are separated from parents with `.` (period), e.g.
`(!address.country|CHAR!)`. (Spaces are not alowed around the `|`.) So a user can do:

$blueShirts = Product::find()
->where(['category' => Product::SHIRT, '(!color!)' => 'blue'])
->all();

$cheapShirts = Product::find()
->select(
['sale' => 'MAX((!cost|decimal(6,2)!), 0.75 * (!price.wholesale.12|decimal(6,2)!))']
)
->where(['category' => Product::SHIRT])
->andWhere('(!price.retail.unit|decimal(6,2)!) < 20.00')
->all();

The implementation is like db\Connection's quoting of [[string]] and {{string}}. Once
the full SQL string is ready, `preg_repalce()` it. The regex pattern is a bit complex
and the replacement callback isn't pretty either. Is there a better way to add to
`$params` in the callback than this? And for the parameter placeholder counter `$i`?

@param null|\yii\db\Connection $db The database connection

@return \yii\db\Command the modified SQL statement
Generate DB command from ActiveQuery with Maria - specific SQL for dynamic columns .