src/Resources/DbSchemaResource.php
protected function buildTableFields(
        $table_schema,
        $fields,
        $allow_update = false,
        $allow_delete = false
    ) {
        if (!is_array($fields) || empty($fields)) {
            throw new \Exception('There are no fields in the requested schema.');
        }

        if (!isset($fields[0])) {
            // single record possibly passed in without wrapper array
            $fields = [$fields];
        }

        $schema = $this->parent->getSchema();
        $columns = [];
        $alterColumns = [];
        $dropColumns = [];
        $references = [];
        $indexes = [];
        $extras = [];
        $dropExtras = [];
        $commands = [];
        $internalTableName = $table_schema->internalName;
        foreach ($fields as $field) {
            $this->cleanClientField($field);
            $name = array_get($field, 'name');

            /** @type ColumnSchema $oldField */
            if ($oldField = $table_schema->getColumn($name)) {
                // UPDATE
                if (!$allow_update) {
                    throw new \Exception("Field '$name' already exists in table '{$table_schema->name}'.");
                }

                $oldArray = $oldField->toArray();
                $diffFields = array_diff($this->fieldExtras, ['picklist', 'validation', 'db_function']);
                $extraNew = array_diff_assoc(array_only($field, $diffFields), array_only($oldArray, $diffFields));

                if (array_key_exists('picklist', $field)) {
                    $picklist = (array)array_get($field, 'picklist');
                    $oldPicklist = (array)$oldField->picklist;
                    if ((count($picklist) !== count($oldPicklist)) ||
                        !empty(array_diff($picklist, $oldPicklist)) ||
                        !empty(array_diff($oldPicklist, $picklist))
                    ) {
                        $extraNew['picklist'] = $picklist;
                    }
                }

                if (array_key_exists('validation', $field)) {
                    $validation = (array)array_get($field, 'validation');
                    $oldValidation = (array)$oldField->validation;
                    if (json_encode($validation) !== json_encode($oldValidation)) {
                        $extraNew['validation'] = $validation;
                    }
                }

                if (array_key_exists('db_function', $field)) {
                    $dbFunction = (array)array_get($field, 'db_function');
                    $oldFunction = (array)$oldField->dbFunction;
                    if (json_encode($dbFunction) !== json_encode($oldFunction)) {
                        $extraNew['db_function'] = $dbFunction;
                    }
                }

                // clean out extras
                $noDiff = array_merge($this->fieldExtras, ['default', 'native']);
                $settingsNew = array_diff_assoc(array_except($field, $noDiff), array_except($oldArray, $noDiff));

                // may be an array due to expressions
                if (array_key_exists('default', $settingsNew)) {
                    $default = $settingsNew['default'];
                    if ($default !== $oldField->defaultValue) {
                        $settingsNew['default'] = $default;
                    }
                }
                if (array_key_exists('native', $settingsNew)) {
                    $native = $settingsNew['native'];
                    if ($native !== $oldField->native) {
                        $settingsNew['native'] = $native;
                    }
                }

                // if empty, nothing to do here, check extras
                if (empty($settingsNew)) {
                    if (!empty($extraNew)) {
                        $extraNew['table'] = $table_schema->name;
                        $extraNew['field'] = $name;
                        $extras[] = $extraNew;
                    }

                    continue;
                }

                $type = strtolower((string)array_get($field, 'type'));
                if (!$schema->supportsResourceType(DbResourceTypes::TYPE_TABLE_FIELD) ||
                    array_get($extraNew, 'is_virtual', false) || $oldField->isVirtual
                ) {
                    if (!$oldField->isVirtual) {
                        throw new \Exception("Field '$name' already exists as non-virtual in table '{$table_schema->name}'.");
                    }
                    // no need to build what the db doesn't support, use extras and bail
                    $extraNew['extra_type'] = $type;
                } else {
                    if ($schema->isUndiscoverableType($type)) {
                        $extraNew['extra_type'] = $type;
                    }

                    $result = $this->buildTableField($internalTableName, $field, true, $oldField);
                    $commands = array_merge($commands, (array)array_get($result, 'commands'));
                    $references = array_merge($references, (array)array_get($result, 'references'));
                    $indexes = array_merge($indexes, (array)array_get($result, 'indexes'));

                    $alterColumns[$name] = $field;
                }
            } else {
                // CREATE

                // clean out extras
                $extraNew = array_only($field, $this->fieldExtras);
                $field = array_except($field, $this->fieldExtras);

                $type = strtolower((string)array_get($field, 'type'));
                if (!$schema->supportsResourceType(DbResourceTypes::TYPE_TABLE_FIELD) ||
                    array_get($extraNew, 'is_virtual', false)
                ) {
                    // no need to build what the db doesn't support, use extras and bail
                    $extraNew['extra_type'] = $type;
                } else {
                    if ($schema->isUndiscoverableType($type)) {
                        $extraNew['extra_type'] = $type;
                    }

                    $result = $this->buildTableField($internalTableName, $field, true);
                    $commands = array_merge($commands, (array)array_get($result, 'commands'));
                    $references = array_merge($references, (array)array_get($result, 'references'));
                    $indexes = array_merge($indexes, (array)array_get($result, 'indexes'));

                    $columns[$name] = $field;
                }
            }

            if (!empty($extraNew)) {
                $extraNew['table'] = $table_schema->name;
                $extraNew['field'] = $name;
                $extras[] = $extraNew;
            }
        }

        if ($allow_delete) {
            // check for columns to drop
            /** @type  ColumnSchema $oldField */
            foreach ($table_schema->getColumns() as $oldField) {
                $found = false;
                foreach ($fields as $field) {
                    $field = array_change_key_case($field, CASE_LOWER);
                    if (array_get($field, 'name') === $oldField->name) {
                        $found = true;
                    }
                }
                if (!$found) {
                    if ($schema->supportsResourceType(DbResourceTypes::TYPE_TABLE_FIELD) && !$oldField->isVirtual) {
                        $dropColumns[] = $oldField->name;
                    }
                    $dropExtras[$table_schema->name][] = $oldField->name;
                }
            }
        }

        return [
            'columns'       => $columns,
            'alter_columns' => $alterColumns,
            'drop_columns'  => $dropColumns,
            'references'    => $references,
            'indexes'       => $indexes,
            'extras'        => $extras,
            'drop_extras'   => $dropExtras,
            'commands'      => $commands,
        ];
    }
protected function buildTableFields(
        $table_schema,
        $fields,
        $allow_update = false,
        $allow_delete = false
    ) {
        if (!is_array($fields) || empty($fields)) {
            throw new \Exception('There are no fields in the requested schema.');
        }

        if (!isset($fields[0])) {
            // single record possibly passed in without wrapper array
            $fields = [$fields];
        }

        $schema = $this->parent->getSchema();
        $columns = [];
        $alterColumns = [];
        $dropColumns = [];
        $references = [];
        $indexes = [];
        $extras = [];
        $dropExtras = [];
        $commands = [];
        $internalTableName = $table_schema->internalName;
        foreach ($fields as $field) {
            $this->cleanClientField($field);
            $name = array_get($field, 'name');

            /** @type ColumnSchema $oldField */
            if ($oldField = $table_schema->getColumn($name)) {
                // UPDATE
                if (!$allow_update) {
                    throw new \Exception("Field '$name' already exists in table '{$table_schema->name}'.");
                }

                $oldArray = $oldField->toArray();
                $diffFields = array_diff($this->fieldExtras, ['picklist', 'validation', 'db_function']);
                $extraNew = array_diff_assoc(array_only($field, $diffFields), array_only($oldArray, $diffFields));

                if (array_key_exists('picklist', $field)) {
                    $picklist = (array)array_get($field, 'picklist');
                    $oldPicklist = (array)$oldField->picklist;
                    if ((count($picklist) !== count($oldPicklist)) ||
                        !empty(array_diff($picklist, $oldPicklist)) ||
                        !empty(array_diff($oldPicklist, $picklist))
                    ) {
                        $extraNew['picklist'] = $picklist;
                    }
                }

                if (array_key_exists('validation', $field)) {
                    $validation = (array)array_get($field, 'validation');
                    $oldValidation = (array)$oldField->validation;
                    if (json_encode($validation) !== json_encode($oldValidation)) {
                        $extraNew['validation'] = $validation;
                    }
                }

                if (array_key_exists('db_function', $field)) {
                    $dbFunction = (array)array_get($field, 'db_function');
                    $oldFunction = (array)$oldField->dbFunction;
                    if (json_encode($dbFunction) !== json_encode($oldFunction)) {
                        $extraNew['db_function'] = $dbFunction;
                    }
                }

                // clean out extras
                $noDiff = array_merge($this->fieldExtras, ['default', 'native']);
                $settingsNew = array_diff_assoc(array_except($field, $noDiff), array_except($oldArray, $noDiff));

                // may be an array due to expressions
                if (array_key_exists('default', $settingsNew)) {
                    $default = $settingsNew['default'];
                    if ($default !== $oldField->defaultValue) {
                        $settingsNew['default'] = $default;
                    }
                }
                if (array_key_exists('native', $settingsNew)) {
                    $native = $settingsNew['native'];
                    if ($native !== $oldField->native) {
                        $settingsNew['native'] = $native;
                    }
                }

                // if empty, nothing to do here, check extras
                if (empty($settingsNew)) {
                    if (!empty($extraNew)) {
                        $extraNew['table'] = $table_schema->name;
                        $extraNew['field'] = $name;
                        $extras[] = $extraNew;
                    }

                    continue;
                }

                $type = strtolower((string)array_get($field, 'type'));
                if (!$schema->supportsResourceType(DbResourceTypes::TYPE_TABLE_FIELD) ||
                    array_get($extraNew, 'is_virtual', false) || $oldField->isVirtual
                ) {
                    if (!$oldField->isVirtual) {
                        throw new \Exception("Field '$name' already exists as non-virtual in table '{$table_schema->name}'.");
                    }
                    // no need to build what the db doesn't support, use extras and bail
                    $extraNew['extra_type'] = $type;
                } else {
                    if ($schema->isUndiscoverableType($type)) {
                        $extraNew['extra_type'] = $type;
                    }

                    $result = $this->buildTableField($internalTableName, $field, true, $oldField);
                    $commands = array_merge($commands, (array)array_get($result, 'commands'));
                    $references = array_merge($references, (array)array_get($result, 'references'));
                    $indexes = array_merge($indexes, (array)array_get($result, 'indexes'));

                    $alterColumns[$name] = $field;
                }
            } else {
                // CREATE

                // clean out extras
                $extraNew = array_only($field, $this->fieldExtras);
                $field = array_except($field, $this->fieldExtras);

                $type = strtolower((string)array_get($field, 'type'));
                if (!$schema->supportsResourceType(DbResourceTypes::TYPE_TABLE_FIELD) ||
                    array_get($extraNew, 'is_virtual', false)
                ) {
                    // no need to build what the db doesn't support, use extras and bail
                    $extraNew['extra_type'] = $type;
                } else {
                    if ($schema->isUndiscoverableType($type)) {
                        $extraNew['extra_type'] = $type;
                    }

                    $result = $this->buildTableField($internalTableName, $field, true);
                    $commands = array_merge($commands, (array)array_get($result, 'commands'));
                    $references = array_merge($references, (array)array_get($result, 'references'));
                    $indexes = array_merge($indexes, (array)array_get($result, 'indexes'));

                    $columns[$name] = $field;
                }
            }

            if (!empty($extraNew)) {
                $extraNew['table'] = $table_schema->name;
                $extraNew['field'] = $name;
                $extras[] = $extraNew;
            }
        }

        if ($allow_delete) {
            // check for columns to drop
            /** @type  ColumnSchema $oldField */
            foreach ($table_schema->getColumns() as $oldField) {
                $found = false;
                foreach ($fields as $field) {
                    $field = array_change_key_case($field, CASE_LOWER);
                    if (array_get($field, 'name') === $oldField->name) {
                        $found = true;
                    }
                }
                if (!$found) {
                    if ($schema->supportsResourceType(DbResourceTypes::TYPE_TABLE_FIELD) && !$oldField->isVirtual) {
                        $dropColumns[] = $oldField->name;
                    }
                    $dropExtras[$table_schema->name][] = $oldField->name;
                }
            }
        }

        return [
            'columns'       => $columns,
            'alter_columns' => $alterColumns,
            'drop_columns'  => $dropColumns,
            'references'    => $references,
            'indexes'       => $indexes,
            'extras'        => $extras,
            'drop_extras'   => $dropExtras,
            'commands'      => $commands,
        ];
    }
@param TableSchema $table_schema
@param array       $fields
@param bool        $allow_update
@param bool        $allow_delete

@throws \Exception
@return array