/**
  * Process models
  *
  * @param array $options
  * @return array
  */
 public static function process_models($options = [])
 {
     $result = ['success' => false, 'error' => [], 'hint' => [], 'data' => []];
     do {
         // we need to process all dependencies first
         $dep = self::process_deps_all($options);
         if (!$dep['success']) {
             $result = $dep;
             $result['error'][] = 'You must fix all dependency related errors first before processing models.';
             break;
         }
         // proccesing models
         if (empty($dep['data']['model_processed'])) {
             $result['error'][] = 'You do not have models to process!';
             break;
         }
         $object_attributes = [];
         $object_relations = [];
         $object_forms = [];
         $flag_relation = application::get('dep.submodule.numbers.data.relations') ? true : false;
         $object_documentation = [];
         $object_import = [];
         $ddl = new numbers_backend_db_class_ddl();
         // run 1 to deterine virtual tables
         $first = true;
         $virtual_models = $dep['data']['model_processed'];
         run_again:
         foreach ($virtual_models as $k => $v) {
             $k2 = str_replace('.', '_', $k);
             if ($v == 'object_table') {
                 $model = factory::model($k2, true);
                 foreach (['attributes', 'audit', 'addresses'] as $v0) {
                     if ($model->{$v0}) {
                         $v01 = $v0 . '_model';
                         $virtual_models[str_replace('_', '.', $model->{$v01})] = 'object_table';
                     }
                 }
             }
         }
         if ($first) {
             $first = false;
             goto run_again;
             // some widgets have attributes
         }
         $dep['data']['model_processed'] = array_merge_hard($dep['data']['model_processed'], $virtual_models);
         $domains = object_data_domains::get_static();
         // run 2
         foreach ($dep['data']['model_processed'] as $k => $v) {
             $k2 = str_replace('.', '_', $k);
             if ($v == 'object_table') {
                 $model = factory::model($k2, true);
                 $temp_result = $ddl->process_table_model($model);
                 if (!$temp_result['success']) {
                     array_merge3($result['error'], $temp_result['error']);
                 }
                 $object_documentation[$v][$k2] = $k2;
                 // relation
                 if ($flag_relation) {
                     if (!empty($model->relation)) {
                         $domain = $model->columns[$model->relation['field']]['domain'] ?? null;
                         if (!empty($domain)) {
                             $domain = str_replace('_sequence', '', $domain);
                             $type = $domains[$domain]['type'];
                         } else {
                             $type = $model->columns[$model->relation['field']]['type'];
                         }
                         $object_relations[$k2] = ['rn_relattr_code' => $model->relation['field'], 'rn_relattr_name' => $model->title, 'rn_relattr_model' => $k2, 'rn_relattr_domain' => $domain, 'rn_relattr_type' => $type, 'rn_relattr_inactive' => !empty($model->relation['inactive']) ? 1 : 0];
                     }
                     if (!empty($model->attributes)) {
                         $object_attributes[$k2] = ['rn_attrmdl_code' => $k2, 'rn_attrmdl_name' => $model->title, 'rn_attrmdl_inactive' => 0];
                     }
                 }
             } else {
                 if ($v == 'object_sequence') {
                     $temp_result = $ddl->process_sequence_model($k2);
                     if (!$temp_result['success']) {
                         array_merge3($result['error'], $temp_result['error']);
                     }
                     $object_documentation[$v][$k2] = $k2;
                 } else {
                     if ($v == 'object_function') {
                         $temp_result = $ddl->process_function_model($k2);
                         if (!$temp_result['success']) {
                             array_merge3($result['error'], $temp_result['error']);
                         }
                         $object_documentation[$v][$k2] = $k2;
                     } else {
                         if ($v == 'object_extension') {
                             $temp_result = $ddl->process_function_extension($k2);
                             if (!$temp_result['success']) {
                                 array_merge3($result['error'], $temp_result['error']);
                             }
                             $object_documentation[$v][$k2] = $k2;
                         } else {
                             if ($v == 'object_import') {
                                 $object_import[$k2] = ['model' => $k2];
                             }
                         }
                     }
                 }
             }
         }
         // if we have erros
         if (!empty($result['error'])) {
             break;
         }
         // db factory
         $db_factory = factory::get('db');
         // we load objects from database
         $loaded_objects = [];
         foreach ($ddl->db_links as $k => $v) {
             $ddl_object = $db_factory[$k]['ddl_object'];
             $temp_result = $ddl_object->load_schema($k);
             if (!$temp_result['success']) {
                 array_merge3($result['error'], $temp_result['error']);
             } else {
                 $loaded_objects[$k] = $temp_result['data'];
             }
         }
         // if we have erros
         if (!empty($result['error'])) {
             break;
         }
         // get a list of all db links
         $db_link_list = array_unique(array_merge(array_keys($ddl->objects), array_keys($loaded_objects)));
         // if we are dropping schema
         if ($options['mode'] == 'drop') {
             $ddl->objects = [];
         }
         // compare schemas per db link
         $schema_diff = [];
         $total_per_db_link = [];
         $total = 0;
         foreach ($db_link_list as $k) {
             // we need to have a back end for comparison
             $compare_options['backend'] = $db_factory[$k]['backend'];
             // comparing
             $temp_result = $ddl->compare_schemas(isset($ddl->objects[$k]) ? $ddl->objects[$k] : [], isset($loaded_objects[$k]) ? $loaded_objects[$k] : [], $compare_options);
             if (!$temp_result['success']) {
                 array_merge3($result['hint'], $temp_result['error']);
             } else {
                 $schema_diff[$k] = $temp_result['data'];
                 if (!isset($total_per_db_link[$k])) {
                     $total_per_db_link[$k] = 0;
                 }
                 $total_per_db_link[$k] += $temp_result['count'];
                 $total += $temp_result['count'];
             }
         }
         // if there's no schema changes
         if ($total == 0) {
             if ($options['mode'] == 'commit') {
                 goto import_data;
             } else {
                 $result['success'] = true;
             }
             break;
         }
         // we need to provide a list of changes
         foreach ($total_per_db_link as $k => $v) {
             $result['hint'][] = '';
             $result['hint'][] = "Db link {$k} requires {$v} changes!";
             // printing summary
             $result['hint'][] = ' * Link ' . $k . ': ';
             foreach ($schema_diff[$k] as $k2 => $v2) {
                 $result['hint'][] = '   * ' . $k2 . ': ';
                 foreach ($v2 as $k3 => $v3) {
                     $result['hint'][] = '    * ' . $k3 . ' - ' . $v3['type'];
                 }
             }
         }
         // if we are in no commit mode we exit
         if (!in_array($options['mode'], ['commit', 'drop'])) {
             break;
         }
         // generating sql
         foreach ($total_per_db_link as $k => $v) {
             if ($v == 0) {
                 continue;
             }
             $ddl_object = $db_factory[$k]['ddl_object'];
             foreach ($schema_diff[$k] as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     // we need to make fk constraints last to sort MySQL issues
                     if ($k2 == 'new_constraints' && $v3['type'] == 'constraint_new' && $v3['data']['type'] == 'fk') {
                         $schema_diff[$k][$k2 . '_fks'][$k3]['sql'] = $ddl_object->render_sql($v3['type'], $v3);
                     } else {
                         $schema_diff[$k][$k2][$k3]['sql'] = $ddl_object->render_sql($v3['type'], $v3, ['mode' => $options['mode']]);
                     }
                 }
             }
         }
         //			print_r($schema_diff);
         //			exit;
         // executing sql
         foreach ($total_per_db_link as $k => $v) {
             if ($v == 0) {
                 continue;
             }
             $db_object = new db($k);
             // if we are dropping we need to disable foregn key checks
             if ($options['mode'] == 'drop') {
                 if ($db_object->backend == 'mysqli') {
                     $db_object->query('SET foreign_key_checks = 0;');
                     // we also need to unset sequences
                     unset($schema_diff[$k]['delete_sequences']);
                 }
             }
             foreach ($schema_diff[$k] as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     if (empty($v3['sql'])) {
                         continue;
                     }
                     if (is_array($v3['sql'])) {
                         $temp = $v3['sql'];
                     } else {
                         $temp = [$v3['sql']];
                     }
                     foreach ($temp as $v4) {
                         $temp_result = $db_object->query($v4);
                         if (!$temp_result['success']) {
                             array_merge3($result['error'], $temp_result['error']);
                             goto error;
                         }
                     }
                 }
             }
         }
         // if we got here - we are ok
         $result['success'] = true;
     } while (0);
     import_data:
     // we need to import data
     if (!empty($object_import) && $options['mode'] == 'commit') {
         $result['hint'][] = '';
         foreach ($object_import as $k => $v) {
             $data_object = new $k();
             $data_result = $data_object->process();
             if (!$data_result['success']) {
                 throw new Exception(implode("\n", $data_result['error']));
             }
             $result['hint'] = array_merge($result['hint'], $data_result['hint']);
         }
     }
     // relation
     if ($flag_relation && $options['mode'] == 'commit') {
         $result['hint'][] = '';
         $model2 = factory::model('numbers_data_relations_model_relation_attributes');
         // insert new models
         if (!empty($object_relations)) {
             foreach ($object_relations as $k => $v) {
                 $result_insert = $model2->save($v, ['pk' => ['rn_relattr_code'], 'ignore_not_set_fields' => true]);
             }
             $result['hint'][] = ' * Imported relation models!';
         }
         // we need to process forms
         foreach ($dep['data']['submodule_dirs'] as $v) {
             $dir = $v . 'model/form/';
             if (!file_exists($dir)) {
                 continue;
             }
             $files = helper_file::iterate($dir, ['only_extensions' => ['php']]);
             foreach ($files as $v2) {
                 $model_name = str_replace(['../libraries/vendor/', '.php'], '', $v2);
                 $model_name = str_replace('/', '_', $model_name);
                 $model = new $model_name(['skip_processing' => true]);
                 if (empty($model->form_object->misc_settings['option_models'])) {
                     continue;
                 }
                 // loop though fields
                 foreach ($model->form_object->misc_settings['option_models'] as $k3 => $v3) {
                     $object_forms[$model_name . '::' . $k3] = ['rn_relfrmfld_form_code' => $model_name, 'rn_relfrmfld_form_name' => $model->title, 'rn_relfrmfld_field_code' => $k3, 'rn_relfrmfld_field_name' => $v3['field_name'], 'rn_relfrmfld_relattr_id' => $v3['model'], 'rn_relfrmfld_inactive' => 0];
                 }
             }
         }
         if (!empty($object_forms)) {
             // load all relation models
             $data = $model2->get(['pk' => ['rn_relattr_model']]);
             $model = factory::model('numbers_data_relations_model_relation_formfields');
             foreach ($object_forms as $k => $v) {
                 if (empty($data[$v['rn_relfrmfld_relattr_id']])) {
                     continue;
                 }
                 $v['rn_relfrmfld_relattr_id'] = $data[$v['rn_relfrmfld_relattr_id']]['rn_relattr_id'];
                 $result_insert = $model->save($v, ['pk' => ['rn_relfrmfld_form_code', 'rn_relfrmfld_field_code'], 'ignore_not_set_fields' => true]);
             }
             $result['hint'][] = ' * Imported relation form fields!';
         }
         // todo: import models
         //print_r2($object_attributes);
         if (!empty($object_attributes)) {
             $model = factory::model('numbers_data_relations_model_attribute_models');
             foreach ($object_attributes as $k => $v) {
                 $result_insert = $model->save($v, ['pk' => ['rn_attrmdl_code'], 'ignore_not_set_fields' => true]);
             }
             $result['hint'][] = ' * Imported attribute models!';
         }
     }
     // we need to generate documentation
     $system_documentation = application::get('system_documentation');
     if (!empty($system_documentation) && $options['mode'] == 'commit') {
         $model = factory::model($system_documentation['model']);
         /*
         print_r2($object_documentation);
         $documentation_result = $model->update($object_documentation, $system_documentation);
         if (!$documentation_result['success']) {
         	$result['error'] = array_merge($result['error'], $documentation_result['error']);
         }
         */
     }
     error:
     return $result;
 }
Beispiel #2
0
 /**
  * Compare two schemas
  *
  * @param array $obj_master
  * @param array $obj_slave
  * @return array
  */
 public function compare_schemas($obj_master, $obj_slave, $options = [])
 {
     $result = ['success' => false, 'error' => [], 'data' => [], 'count' => 0];
     // delete first
     $result['data']['delete_triggers'] = [];
     $result['data']['delete_views'] = [];
     $result['data']['delete_constraints'] = [];
     $result['data']['delete_indexes'] = [];
     $result['data']['delete_functions'] = [];
     $result['data']['delete_columns'] = [];
     $result['data']['delete_tables'] = [];
     $result['data']['delete_domains'] = [];
     // after tables and columns
     $result['data']['delete_sequences'] = [];
     // after domains
     $result['data']['delete_schemas'] = [];
     $result['data']['delete_extensions'] = [];
     // last
     // new second
     $result['data']['new_extensions'] = [];
     // first
     $result['data']['new_schemas'] = [];
     $result['data']['new_schema_owners'] = [];
     $result['data']['new_domains'] = [];
     // after schema
     $result['data']['new_domain_owners'] = [];
     $result['data']['new_sequences'] = [];
     $result['data']['new_sequence_owners'] = [];
     $result['data']['new_tables'] = [];
     $result['data']['new_table_owners'] = [];
     $result['data']['new_columns'] = [];
     $result['data']['change_columns'] = [];
     $result['data']['new_constraints'] = [];
     $result['data']['new_indexes'] = [];
     $result['data']['new_views'] = [];
     // views goes after we add columns
     $result['data']['change_views'] = [];
     $result['data']['new_view_owners'] = [];
     $result['data']['new_functions'] = [];
     $result['data']['new_function_owner'] = [];
     $result['data']['new_triggers'] = [];
     // after functions
     $result['data']['change_triggers'] = [];
     // add extension
     if (!empty($obj_master['extension'])) {
         foreach ($obj_master['extension'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_slave['extension'][$k][$k2])) {
                     $result['data']['new_extensions'][$k . '.' . $k2] = array('type' => 'extension', 'data' => $v2);
                     $result['count']++;
                 }
             }
         }
     }
     // delete extensions
     if (!empty($obj_slave['extension'])) {
         foreach ($obj_slave['extension'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_master['extension'][$k][$k2])) {
                     $result['data']['delete_extensions'][$k . '.' . $k2] = array('type' => 'extension_delete', 'data' => $v2);
                     $result['count']++;
                 }
             }
         }
     }
     // new schemas
     if (!empty($obj_master['schema'])) {
         foreach ($obj_master['schema'] as $k => $v) {
             if (empty($obj_slave['schema'][$k])) {
                 $result['data']['new_schemas'][$k] = array('type' => 'schema', 'data' => $v);
                 $result['count']++;
             } else {
                 if ($v['owner'] != $obj_slave['schema'][$k]['owner']) {
                     $result['data']['new_schema_owners'][$k] = array('type' => 'schema_owner', 'data' => $v);
                     $result['count']++;
                 }
             }
         }
     }
     // delete schema
     if (!empty($obj_slave['schema'])) {
         foreach ($obj_slave['schema'] as $k => $v) {
             if (empty($obj_master['schema'][$k])) {
                 $result['data']['delete_schemas'][$k] = array('type' => 'schema_delete', 'data' => $v);
                 $result['count']++;
             }
         }
     }
     // new tables
     if (!empty($obj_master['table'])) {
         foreach ($obj_master['table'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_slave['table'][$k][$k2])) {
                     $result['data']['new_tables'][$v2['full_table_name']] = array('type' => 'table_new', 'data' => $v2);
                     $result['count']++;
                 } else {
                     if ($v2['owner'] != $obj_slave['table'][$k][$k2]['owner']) {
                         $result['data']['new_table_owners'][$v2['full_table_name']] = array('type' => 'table_owner', 'data' => $v2);
                         $result['count']++;
                     }
                 }
             }
         }
     }
     // delete table
     if (isset($obj_slave['table'])) {
         foreach ($obj_slave['table'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_master['table'][$k][$k2])) {
                     $result['data']['delete_tables'][$v2['full_table_name']] = array('type' => 'table_delete', 'data' => $v2);
                     $result['count']++;
                 }
             }
         }
     }
     // new columns
     if (!empty($obj_master['table'])) {
         foreach ($obj_master['table'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 // if we have new table we do not need to check for new columns
                 if (!empty($result['data']['new_tables'][$v2['full_table_name']])) {
                     continue;
                 }
                 // finding new column
                 foreach ($v2['columns'] as $k3 => $v3) {
                     if (empty($obj_slave['table'][$k][$k2]['columns'][$k3])) {
                         $result['data']['new_columns'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'column_new', 'name' => $k3, 'table' => $v2['full_table_name'], 'data' => $v3);
                         $result['count']++;
                     } else {
                         // comparing data types
                         $temp_error = false;
                         if ($v3['type'] != $obj_slave['table'][$k][$k2]['columns'][$k3]['type']) {
                             if (strpos($v3['type'], 'serial') !== false || strpos($obj_slave['table'][$k][$k2]['columns'][$k3]['type'], 'serial') !== false) {
                                 $result['error'][] = 'Serial data type changes must be handled manually, column ' . $k . '.' . $k2 . '.' . $k3;
                                 return $result;
                             }
                             $temp_error = true;
                         }
                         if (!isset($v3['null'])) {
                             $v3['null'] = false;
                         }
                         if (!isset($obj_slave['table'][$k][$k2]['columns'][$k3]['null'])) {
                             $obj_slave['table'][$k][$k2]['columns'][$k3]['null'] = false;
                         }
                         if ($v3['null'] != $obj_slave['table'][$k][$k2]['columns'][$k3]['null']) {
                             $temp_error = true;
                         }
                         if (!isset($v3['default'])) {
                             $v3['default'] = null;
                         }
                         if (!isset($obj_slave['table'][$k][$k2]['columns'][$k3]['default'])) {
                             $obj_slave['table'][$k][$k2]['columns'][$k3]['default'] = null;
                         }
                         if ($v3['default'] != $obj_slave['table'][$k][$k2]['columns'][$k3]['default']) {
                             $temp_error = true;
                         }
                         // auto_increment for mysqli
                         if ($options['backend'] == 'mysqli') {
                             if (!isset($v3['auto_increment'])) {
                                 $v3['auto_increment'] = 0;
                             }
                             if ($v3['auto_increment'] != $obj_slave['table'][$k][$k2]['columns'][$k3]['auto_increment']) {
                                 $temp_error = true;
                             }
                         }
                         if ($temp_error) {
                             $result['data']['change_columns'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'column_change', 'name' => $k3, 'table' => $v2['full_table_name'], 'data' => $v3, 'data_slave' => $obj_slave['table'][$k][$k2]['columns'][$k3]);
                             $result['count']++;
                         }
                     }
                 }
                 // finding columns to be deleted
                 foreach ($obj_slave['table'][$k][$k2]['columns'] as $k3 => $v3) {
                     if (empty($obj_master['table'][$k][$k2]['columns'][$k3])) {
                         $result['data']['delete_columns'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'column_delete', 'name' => $k3, 'table' => $v2['full_table_name']);
                         $result['count']++;
                     }
                 }
             }
         }
     }
     // exceptions for mysqli submodule
     if ($options['backend'] == 'mysqli' && !empty($obj_slave['constraint'])) {
         foreach ($obj_slave['constraint'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     if ($v3['type'] == 'pk') {
                         $found = null;
                         if (!empty($obj_master['constraint'][$k][$k2])) {
                             foreach ($obj_master['constraint'][$k][$k2] as $k4 => $v4) {
                                 if ($v4['type'] == 'pk') {
                                     $found = $k4;
                                     break;
                                 }
                             }
                         }
                         if (!empty($found)) {
                             $temp = $obj_slave['constraint'][$k][$k2][$k3];
                             unset($obj_slave['constraint'][$k][$k2][$k3]);
                             $obj_slave['constraint'][$k][$k2][$found] = $temp;
                         }
                     }
                 }
             }
         }
     }
     // new constraints
     if (!empty($obj_master['constraint'])) {
         foreach ($obj_master['constraint'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     if (empty($obj_slave['constraint'][$k][$k2][$k3])) {
                         $result['data']['new_constraints'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'constraint_new', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                         $result['count']++;
                     } else {
                         // comparing structure
                         $temp_error = false;
                         if ($v3['type'] != $obj_slave['constraint'][$k][$k2][$k3]['type']) {
                             $temp_error = true;
                         }
                         if ($v3['full_table_name'] != $obj_slave['constraint'][$k][$k2][$k3]['full_table_name']) {
                             $temp_error = true;
                         }
                         if (!array_compare_level1($v3['columns'], $obj_slave['constraint'][$k][$k2][$k3]['columns'])) {
                             $temp_error = true;
                         }
                         // additiona verifications for fk constraints
                         if ($v3['type'] == 'fk') {
                             if ($v3['foreign_table'] != $obj_slave['constraint'][$k][$k2][$k3]['foreign_table']) {
                                 $temp_error = true;
                             }
                             if (!array_compare_level1($v3['foreign_columns'], $obj_slave['constraint'][$k][$k2][$k3]['foreign_columns'])) {
                                 $temp_error = true;
                             }
                         }
                         // if we have an error we rebuild
                         if ($temp_error) {
                             $result['data']['delete_constraints'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'constraint_delete', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                             $result['data']['new_constraints'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'constraint_new', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                             $result['count'] += 1;
                         }
                     }
                 }
             }
         }
     }
     // delete constraints
     if (isset($obj_slave['constraint'])) {
         foreach ($obj_slave['constraint'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     if (empty($obj_master['constraint'][$k][$k2][$k3])) {
                         $result['data']['delete_constraints'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'constraint_delete', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                         $result['count']++;
                     }
                 }
             }
         }
     }
     // new indexes
     if (!empty($obj_master['index'])) {
         foreach ($obj_master['index'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     if (empty($obj_slave['index'][$k][$k2][$k3])) {
                         $result['data']['new_indexes'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'index_new', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                         $result['count']++;
                     } else {
                         // comparing structure
                         $temp_error = false;
                         if ($v3['type'] != $obj_slave['index'][$k][$k2][$k3]['type']) {
                             $temp_error = true;
                         }
                         if (!array_compare_level1($v3['columns'], $obj_slave['index'][$k][$k2][$k3]['columns'])) {
                             $temp_error = true;
                         }
                         // todo: comparison for foreign key & check
                         if ($temp_error) {
                             $result['data']['delete_indexes'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'index_delete', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $obj_slave['index'][$k][$k2][$k3]);
                             $result['data']['new_indexes'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'index_new', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                             $result['count'] += 1;
                         }
                     }
                 }
             }
         }
     }
     // delete indexes
     if (isset($obj_slave['index'])) {
         foreach ($obj_slave['index'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 foreach ($v2 as $k3 => $v3) {
                     if (empty($obj_master['index'][$k][$k2][$k3])) {
                         $result['data']['delete_indexes'][$k . '.' . $k2 . '.' . $k3] = array('type' => 'index_delete', 'name' => $k3, 'table' => $v3['full_table_name'], 'data' => $v3);
                         $result['count']++;
                     }
                 }
             }
         }
     }
     // for mysqli/pgsql we need to put new sequences after new tables
     if ($options['backend'] == 'mysqli' || $options['backend'] == 'pgsql') {
         unset($result['data']['new_sequences']);
         $result['data']['new_sequences'] = [];
     }
     // new sequences
     if (!empty($obj_master['sequence'])) {
         foreach ($obj_master['sequence'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_slave['sequence'][$k][$k2])) {
                     $result['data']['new_sequences'][$k . '.' . $k2] = array('type' => 'sequences_new', 'name' => $v2['full_sequence_name'], 'owner' => $v2['owner'], 'data' => $v2);
                     $result['count']++;
                 } else {
                     if ($options['backend'] == 'pgsql') {
                         // todo: fix here for pgsql!!!
                         // checking owner information
                         if ($v2['owner'] != $obj_slave['sequence'][$k][$k2]['owner']) {
                             $result['data']['new_sequence_owners'][$k . '.' . $k2] = array('type' => 'sequence_owner', 'name' => $v2['full_sequence_name'], 'owner' => $v2['owner'], 'data' => $v2);
                         }
                     }
                 }
             }
         }
     }
     // delete sequences
     if (!empty($obj_slave['sequence'])) {
         foreach ($obj_slave['sequence'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_master['sequence'][$k][$k2])) {
                     $result['data']['delete_sequences'][$k . '.' . $k2] = array('type' => 'sequence_delete', 'name' => $v2['full_sequence_name'], 'data' => $v2);
                     $result['count']++;
                 }
             }
         }
     }
     // functions
     if (!empty($obj_master['function'])) {
         foreach ($obj_master['function'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_slave['function'][$k][$k2])) {
                     $result['data']['new_functions'][$k . '.' . $k2] = array('type' => 'function_new', 'name' => $v2['full_function_name'], 'owner' => $v2['owner'], 'data' => $v2);
                     $result['count']++;
                 } else {
                     // if function has changed
                     if ($options['backend'] == 'mysqli') {
                         if ($v2['sql_parts']['body'] != $obj_slave['function'][$k][$k2]['sql_parts']['body']) {
                             $result['data']['delete_functions'][$k . '.' . $k2] = array('type' => 'function_delete', 'name' => $v2['full_function_name'], 'data' => $v2);
                             $result['data']['new_functions'][$k . '.' . $k2] = array('type' => 'function_new', 'name' => $v2['full_function_name'], 'owner' => $v2['owner'], 'data' => $v2);
                             $result['count']++;
                         }
                     } else {
                         if ($options['backend'] == 'pgsql') {
                             if (numbers_backend_db_class_ddl::sanitize_function($v2['sql_full']) != numbers_backend_db_class_ddl::sanitize_function($obj_slave['function'][$k][$k2]['sql_full'])) {
                                 $result['data']['delete_functions'][$k . '.' . $k2] = array('type' => 'function_delete', 'name' => $v2['full_function_name'], 'data' => $v2);
                                 $result['data']['new_functions'][$k . '.' . $k2] = array('type' => 'function_new', 'name' => $v2['full_function_name'], 'owner' => $v2['owner'], 'data' => $v2);
                                 $result['count']++;
                             } else {
                                 if ($v2['owner'] != $obj_slave['function'][$k][$k2]['owner']) {
                                     // checking owner
                                     $result['data']['new_function_owners'][$k . '.' . $k2] = array('type' => 'function_owner', 'name' => $v2['full_function_name'], 'owner' => $v2['owner'], 'data' => $v2);
                                     $result['count']++;
                                 }
                             }
                         }
                     }
                 }
             }
         }
     }
     // delete function
     if (!empty($obj_slave['function'])) {
         foreach ($obj_slave['function'] as $k => $v) {
             foreach ($v as $k2 => $v2) {
                 if (empty($obj_master['function'][$k][$k2])) {
                     $result['data']['delete_functions'][$k . '.' . $k2] = array('type' => 'function_delete', 'name' => $v2['full_function_name'], 'data' => $v2);
                     $result['count']++;
                 }
             }
         }
     }
     // if we delete tables there's no need to delete constrants and/or indexes
     foreach ($result['data']['delete_tables'] as $k => $v) {
         // unsetting constraints
         foreach ($result['data']['delete_constraints'] as $k2 => $v2) {
             if ($v2['data']['full_table_name'] == $k) {
                 unset($result['data']['delete_constraints'][$k2]);
             }
         }
         // unsetting indexes
         foreach ($result['data']['delete_indexes'] as $k2 => $v2) {
             if ($v2['data']['full_table_name'] == $k) {
                 unset($result['data']['delete_indexes'][$k2]);
             }
         }
     }
     // final step clean up empty keys
     foreach ($result['data'] as $k => $v) {
         // clean up empty nodes
         if (empty($v)) {
             unset($result['data'][$k]);
         }
     }
     $result['success'] = true;
     return $result;
 }