From 2684a2c663daf6ac110bfddcd455da4163787037 Mon Sep 17 00:00:00 2001 From: Pavel Merzlyakov Date: Fri, 8 Sep 2023 17:08:46 +0300 Subject: [PATCH] partial table_json_kv support --- src/codegen.inc.php | 158 +++++++++- tpl/macros_rpc.twig | 8 +- tpl/macros_struct.twig | 642 +++++++++++++++++++---------------------- 3 files changed, 452 insertions(+), 356 deletions(-) diff --git a/src/codegen.inc.php b/src/codegen.inc.php index a69367d..cc1ae7c 100644 --- a/src/codegen.inc.php +++ b/src/codegen.inc.php @@ -196,7 +196,7 @@ function add_twig_filters(\Twig\Environment $twig) { $type = $type->getValue(); } - $pkey = explode(',', $type->getToken('table_pkey')); + $pkey = table_pkey_fields($type); $fields = []; foreach ($pkey as $fieldName) { $fields[] = $type->getField($fieldName); @@ -204,6 +204,116 @@ function add_twig_filters(\Twig\Environment $twig) return $fields; } )); + $twig->addFilter(new TwigFilter( + 'raw_fields', + function(\mtgMetaStruct $struct): array { + $json_fields = table_json_fields($struct); + return array_filter( + $struct->getFields(), + fn(\mtgMetaField $field): bool => !in_array($field->getName(), $json_fields), + ); + } + )); + $twig->addFilter(new TwigFilter( + 'raw_nonpk_fields', + function(\mtgMetaStruct $struct): array { + $pkey = table_pkey_fields($struct); + $json_fields = table_json_fields($struct); + return array_filter( + $struct->getFields(), + fn(\mtgMetaField $field): bool => !in_array($field->getName(), $pkey) && !in_array($field->getName(), $json_fields), + ); + } + )); + $twig->addFilter(new TwigFilter( + 'json_fields', + function(\mtgMetaStruct $struct): array { + $json_fields = table_json_fields($struct); + return array_map( + fn(string $field): \mtgMetaField => $struct->getField($field), + $json_fields, + ); + } + )); + $twig->addFilter(new TwigFilter( + 'table_columns', + function(\mtgMetaStruct $struct): array { + $json_fields = table_json_fields($struct); + + $columns = array_reduce( + $struct->getFields(), + function(array $carry, \mtgMetaField $field) use ($json_fields): array { + if(in_array($field->getName(), $json_fields)) + { + return $carry; + } + $carry[] = $field->getName(); + return $carry; + }, + [] + ); + + if(count($json_fields) > 0) + { + $columns[] = 'kv'; + } + + return $columns; + } + )); + $twig->addFilter(new TwigFilter( + 'table_nopk_columns', + function(\mtgMetaStruct $struct): array { + $pkey = table_pkey_fields($struct); + $json_fields = table_json_fields($struct); + + $columns = array_reduce( + $struct->getFields(), + function(array $carry, \mtgMetaField $field) use ($pkey, $json_fields): array { + if(in_array($field->getName(), $pkey) || in_array($field->getName(), $json_fields)) + { + return $carry; + } + $carry[] = $field->getName(); + return $carry; + }, + [] + ); + + if(count($json_fields) > 0) + { + $columns[] = 'kv'; + } + + return $columns; + } + )); + $twig->addFilter(new TwigFilter( + 'table_fields', + function(\mtgMetaStruct $struct): array { + $json_fields = table_json_fields($struct); + + $fields = array_reduce( + $struct->getFields(), + function(array $carry, \mtgMetaField $field) use ($json_fields): array { + if(in_array($field->getName(), $json_fields)) + { + return $carry; + } + $carry[] = snake2camel($field->getName()); + return $carry; + }, + [] + ); + + if(count($json_fields) > 0) + { + $fields[] = json_field_name($struct); + } + + return $fields; + } + )); } function add_twig_functions(\Twig\Environment $twig) @@ -241,18 +351,17 @@ function add_twig_functions(\Twig\Environment $twig) $twig->addFunction(new TwigFunction( 'table_pkey', function(\mtgMetaStruct $struct): array { - $pkey = explode(',', $struct->getToken('table_pkey')); - $fields = []; - foreach ($pkey as $fieldName) { - $fields[] = $struct->getField($fieldName); - } - return $fields; + $pkey = table_pkey_fields($struct); + return array_map( + fn(string $field): \mtgMetaField => $struct->getField($field), + $pkey, + ); } )); $twig->addFunction(new TwigFunction( 'table_fields', function(\mtgMetaStruct $struct): array { - $pkey = explode(',', $struct->getToken('table_pkey')); + $pkey = table_pkey_fields($struct); return array_filter( $struct->getFields(), fn(\mtgMetaField $field): bool => !in_array($field->getName(), $pkey), @@ -350,3 +459,36 @@ function parse_meta_field(string $str, \mtgMetaInfo $meta): \mtgMetaField list($name, $type) = explode('|', $str); return new \mtgMetaField($name, new \mtgTypeRef(new \mtgBuiltinType($type))); } + +function table_pkey_fields(\mtgMetaStruct $struct): array +{ + if(!$struct->hasToken('table_pkey')) + { + return []; + } + $pkey = $struct->getToken('table_pkey'); + if(empty($pkey)) + { + throw new Exception('`table_pkey` token is empty'); + } + return explode(',', $pkey); +} + +function table_json_fields(\mtgMetaStruct $struct): array +{ + if(!$struct->hasToken('table_json_kv')) + { + return []; + } + $json_kv = $struct->getToken('table_json_kv'); + if(empty($json_kv)) + { + throw new Exception('`table_json_kv` token is empty'); + } + return explode(',', $struct->getToken('table_json_kv')); +} + +function json_field_name(\mtgMetaStruct $struct): string +{ + return $struct->getName() . 'JsonValues'; +} \ No newline at end of file diff --git a/tpl/macros_rpc.twig b/tpl/macros_rpc.twig index 192393c..7b267bd 100644 --- a/tpl/macros_rpc.twig +++ b/tpl/macros_rpc.twig @@ -67,8 +67,8 @@ func New{{ req_name }}() *{{ req_name }} { } {{ struct_macros.reset_method(r.req, req_name) }} -{{ struct_macros.meta_read(r.req, req_name) }} -{{ struct_macros.meta_write(r.req, req_name) }} +{{ struct_macros.read_methods(r.req, req_name) }} +{{ struct_macros.write_methods(r.req, req_name) }} type {{ rsp_name }} struct { {%~ for f in r.rsp.fields %} @@ -83,6 +83,6 @@ func New{{ rsp_name }}() *{{ rsp_name }} { } {{ struct_macros.reset_method(r.rsp, rsp_name) }} -{{ struct_macros.meta_read(r.rsp, rsp_name) }} -{{ struct_macros.meta_write(r.rsp, rsp_name) }} +{{ struct_macros.read_methods(r.rsp, rsp_name) }} +{{ struct_macros.write_methods(r.rsp, rsp_name) }} {% endmacro rpc %} diff --git a/tpl/macros_struct.twig b/tpl/macros_struct.twig index 2c1bf76..d0816ee 100644 --- a/tpl/macros_struct.twig +++ b/tpl/macros_struct.twig @@ -1,122 +1,71 @@ {% macro struct(s) %} -{% if has_token(s, 'table_json_kv') %} - {{ _self.struct_json_kv(s) }} -{% else %} - {{ _self.struct_common(s) }} - - {% if has_token(s, 'table') %} - {{ _self.struct_table(s) }} - {% endif %} -{% endif %} - -{% if has_token(s, 'data_root') %} - {{ _self.root_set_pk(s) }} - {{ _self.root_save(s) }} - {{ _self.root_delete(s) }} - {{ _self.root_save_diff(s) }} - {{ _self.root_delete_diff(s) }} - {{ _self.root_load(s) }} -{% endif %} -{% endmacro struct %} - - -{% macro struct_json_kv(s) %} -{% set pkey = table_pkey(s) %} -{% set nopkey = ['kv'] %} -{% set json_fields = table_fields(s) %} -{% set json_fields_type = s.name ~ 'Values' %} -{% set table_name = token(s, 'table') %} -{% set insert_columns = pkey|map(f => f.name)|merge(nopkey) %} -{% set insert_fields = pkey|map(f => f|fname)|merge([json_fields_type]) %} -{% set insert_column_expr = insert_columns|map(c => "`#{c}`")|join(',') %} -{% set insert_values_expr = arr_fill('?', insert_columns|length)|join(',') %} -{% set insert_update_expr = nopkey|map(c => "`#{c}`=VALUES(`#{c}`)")|join(',') %} -{% set update_columns = nopkey %} -{% set select_fields = insert_fields %} -{% set select_column_expr = insert_column_expr %} -{% set select_where_expr = pkey|map(f => "`#{f.name}` = ?")|join(' AND ') %} -{% set select_by = pkey|first %} -{% set delete_by = select_by %} -{% set delete_where_expr = select_where_expr %} -{% set pkey_column_expr = pkey|map(f => "`#{f.name}`")|join(',') %} -{% set pkey_values_expr = arr_fill('?', pkey|length)|join(',') %} - +{% set raw_fields = s|raw_fields %} +{% set json_fields = s|json_fields %} +{% set json_fields_type = s.name ~ 'JsonValues' %} type {{ s.name }} struct { - {%~ for f in pkey %} - {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` - {%~ endfor %} + {% if s.parent %} + {{ s.parent.name }} + {% endif %} + {% for f in raw_fields %} + {{ f|fname }} {{ f.type|go_type(f.tokens) }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` + {% endfor %} + + {% if json_fields|length > 0 %} {{ json_fields_type }} -} - -type {{ json_fields_type }} struct{ - {%~ for f in json_fields %} - {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` - {%~ endfor %} - - changedFields meta.ChangedFields + {% endif %} {% if has_token(s, 'bitfields') %} fieldsMask meta.FieldsMask {% endif %} + + {% if has_token(s, 'table') %} + changedFields meta.ChangedFields + {% endif %} +} + +{{ _self.common_methods(s.name, s.classid) }} +{{ _self.reset_method(s) }} +{{ _self.read_methods(s) }} +{{ _self.write_methods(s) }} + +{% if has_token(s, 'bitfields') %} +{{ _self.bitfields_methods(s.name) }} +{% endif %} + +{% if has_token(s, 'table') %} +{{ _self.table_methods(s) }} +{% endif %} + +{% if has_token(s, 'data_root') %} +{{ _self.data_root_methods(s) }} +{% endif %} + +{% if json_fields|length > 0 %} +type {{ json_fields_type }} struct{ + {%~ for f in json_fields %} + {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` + {%~ endfor %} } {{ _self.json_sql_impl(json_fields_type) }} - -{{ _self.struct_methods(s.name, s.classid) }} -{{ _self.reset_method(s) }} -{{ _self.meta_read(s) }} -{{ _self.meta_write(s) }} - -{% if has_token(s, 'bitfields') %} - {{ _self.bitfields_methods(s.name) }} +{# {{ _self.json_set_expr(json_fields_type, json_fields) }} #} {% endif %} - -{{ _self.table_save(_context) }} -{{ _self.table_load(_context) }} -{{ _self.table_delete(_context) }} -{{ _self.table_delete_diff(_context) }} - -{{ _self.changed_fields(json_fields_type, json_fields) }} -{{ _self.json_set_expr(json_fields_type, json_fields) }} - -func Save{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { - if rec.changedFields.Empty() { - return nil - } - - var builder strings.Builder - builder.Grow({{ 56 + table_name|length + insert_column_expr|length + insert_values_expr|length }}) - - builder.WriteString("INSERT INTO `{{ table_name }}` ({{ insert_column_expr }}) VALUES({{ insert_values_expr }}) ON DUPLICATE KEY UPDATE `kv`=") - - if err := rec.WriteJsonSetExpr(&builder); err != nil { - return err - } - - _, saveErr := dbe.ExecContext(ctx, - builder.String(), - {% for f in insert_fields -%} - rec.{{ f }}, - {% endfor -%} - ) - return errors.WithStack(saveErr) -} - -{{ _self.table_save_coll_diff(s.name) }} -{% endmacro struct_json_kv %} +{% endmacro struct %} -{% macro struct_table(s) %} +{% macro table_methods(s) %} {% set pkey = table_pkey(s) %} {% set nopkey = table_fields(s) %} +{% set raw_fields = s|raw_fields %} +{% set raw_nonpk_fields = s|raw_nonpk_fields %} +{% set json_fields = s|json_fields %} {% set table_name = token(s, 'table') %} -{% set insert_columns = s.fields|map(f => f.name) %} -{% set insert_fields = s.fields|map(f => f|fname) %} -{% set insert_column_expr = s.fields|map(f => f.name|quote)|join(',') %} +{% set insert_columns = s|table_columns %} +{% set insert_fields = s|table_fields %} +{% set insert_column_expr = insert_columns|map(c => c|quote)|join(',') %} {% set insert_values_expr = arr_fill('?', insert_columns|length)|join(',') %} -{% set insert_update_expr = nopkey|map(f => "`#{f.name}`=VALUES(`#{f.name}`)")|join(',') %} -{% set update_columns = nopkey|map(f => f.name) %} +{% set insert_update_expr = s|table_nopk_columns|map(c => "`#{c}`=VALUES(`#{c}`)")|join(',') %} {% set select_fields = insert_fields %} {% set select_column_expr = insert_column_expr %} {% set select_where_expr = pkey|map(f => "`#{f.name}` = ?")|join(' AND ') %} @@ -126,179 +75,12 @@ func Save{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, rec {{ s.name {% set pkey_column_expr = pkey|map(f => "`#{f.name}`")|join(',') %} {% set pkey_values_expr = arr_fill('?', pkey|length)|join(',') %} -{{ _self.changed_fields(s.name, nopkey) }} - -{# {{ _self.struct_identifier(s.name, pkey) }} #} +{{ _self.changed_fields_methods(s.name, nopkey) }} {{ _self.table_save(_context) }} {{ _self.table_load(_context) }} {{ _self.table_delete(_context) }} -{{ _self.table_delete_diff(_context) }} - -func Save{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { - if rec.changedFields.Empty() { - return nil - } - - var queryBuilder strings.Builder - queryBuilder.Grow({{ 52 + table_name|length + insert_column_expr|length + insert_values_expr|length + insert_update_expr|length }}) - queryBuilder.WriteString("INSERT INTO `{{ table_name }}` ({{ pkey_column_expr }}") - - var valuesBuilder strings.Builder - valuesBuilder.Grow({{ 10 + insert_values_expr|length }}) - valuesBuilder.WriteString(" VALUES ({{ pkey_values_expr }}") - - var updateBuilder strings.Builder - updateBuilder.Grow({{ 25 + insert_update_expr|length }}) - updateBuilder.WriteString(" ON DUPLICATE KEY UPDATE ") - - values := make([]any, 0, 10) - {% for f in pkey %} - values = append(values, rec.{{ f|fname }}) - {% endfor %} - - initUpdateLen := updateBuilder.Len() - - {% for f in nopkey %} - if rec.{{ f|fname }}Changed() { - queryBuilder.WriteString(",`{{ f.name }}`") - - if updateBuilder.Len() > initUpdateLen { - updateBuilder.WriteRune(',') - } - updateBuilder.WriteString("`{{ f.name }}`=VALUES(`{{ f.name }}`)") - - valuesBuilder.WriteString(",?") - - values = append(values, rec.{{ f|fname }}) - } - {% endfor %} - - valuesBuilder.WriteRune(')') - - queryBuilder.WriteRune(')') - queryBuilder.WriteString(valuesBuilder.String()) - queryBuilder.WriteString(updateBuilder.String()) - - _, saveErr := dbe.ExecContext(ctx, queryBuilder.String(), values...) - return errors.Wrap(saveErr, queryBuilder.String()) -} - -{{ _self.table_save_coll_diff(s.name) }} -{% endmacro struct_table %} - - -{% macro root_set_pk(s) %} -{% set set_field = meta_field(token(s, 'data_root')) %} -func (s *{{ s.name }}) Set{{ set_field|fname }}({{ set_field|varname }} {{ set_field.type|go_type }}) { - {%~ for f in s.fields|filter(f => not has_token(f, 'db_skip')) %} - {%~ if f.type is array %} - {% set pkey = table_pkey(f.type.value) %} - {% set field_name = pkey|first|fname %} - - for i := range s.{{ f|fname }} { - s.{{ f|fname }}[i].{{ field_name }} = {{ set_field|varname }} - } - {%~ else %} - {% set pkey = table_pkey(f.type) %} - {% set field_name = pkey|first|fname %} - - s.{{ f|fname }}.{{ field_name }} = {{ set_field|varname }} - {%~ endif %} - {%~ endfor %} -} -{% endmacro root_set_pk %} - - -{% macro root_save(s) %} -func Save{{ s.name }}(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { - {%~ for f in s.fields|filter(f => not has_token(f, 'db_skip')) %} - {%~ if f.type is array %} - if err := Save{{ f.type.value.name }}Collection(ctx, dbe, rec.{{ f|fname }}); err != nil { - return err - } - {%~ else %} - if err := Save{{ f.type.name }}(ctx, dbe, rec.{{ f|fname }}); err != nil { - return err - } - {%~ endif %} - {%~ endfor %} - return nil -} -{% endmacro root_save %} - - -{% macro root_save_diff(s) %} -func Save{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { - {%~ for f in s.fields|filter(f => not has_token(f, 'db_skip')) %} - {%~ if f.type is array %} - if err := Save{{ f.type.value.name }}CollectionDiff(ctx, dbe, rec.{{ f|fname }}); err != nil { - return err - } - {%~ else %} - if err := Save{{ f.type.name }}Diff(ctx, dbe, rec.{{ f|fname }}); err != nil { - return err - } - {%~ endif %} - {%~ endfor %} - return nil -} -{% endmacro root_save_diff %} - - -{% macro root_delete(s) %} -{% set delete_by = meta_field(token(s, 'data_root')) %} -func Delete{{ s.name }}(ctx context.Context, dbe metadb.Execer, {{ delete_by|varname }} {{ delete_by.type|go_type }}) error { - {%~ for f in s.fields|filter(f => not has_token(f, 'db_skip')) %} - {%~ if f.type is array %} - if err := Delete{{ f.type.value.name }}Collection(ctx, dbe, {{ delete_by|varname }}); err != nil { - return err - } - {%~ elseif f.type is struct %} - if _, err := Delete{{ f.type.name }}(ctx, dbe, {{ delete_by|varname }}); err != nil { - return err - } - {%~ else %} - if err := Delete{{ f.type.name }}(ctx, dbe, {{ delete_by|varname }}); err != nil { - return err - } - {%~ endif %} - {%~ endfor %} - return nil -} -{% endmacro root_delete %} - - -{% macro root_delete_diff(s) %} -func Delete{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, ids {{ s.name }}RemovedIds) error { - {%~ for f in s.fields|filter(f => not has_token(f, 'db_skip') and f.type is array) %} - if err := Delete{{ f.type.value.name }}CollectionById(ctx, dbe, ids.{{ f|fname }}); err != nil { - return err - } - {%~ endfor %} - return nil -} -{% endmacro root_delete_diff %} - - -{% macro root_load(s) %} -{% set load_by = meta_field(token(s, 'data_root')) %} -func Load{{ s.name }}(ctx context.Context, dbq metadb.Querier, {{ load_by|varname }} {{ load_by.type|go_type }}) ({{ s.name }}, error) { - var s {{ s.name }} - {%~ for f in s.fields|filter(f => not has_token(f, 'db_skip')) %} - {%~ if f.type is array %} - {{ f|varname }}, err := Load{{ f.type.value.name }}Collection(ctx, dbq, {{ load_by|varname }}) - {%~ else %} - {{ f|varname }}, err := Load{{ f.type.name }}(ctx, dbq, {{ load_by|varname }}) - {%~ endif %} - if err != nil { - return {{ s.name }}{}, err - } - s.{{ f|fname }} = {{ f|varname }} - {%~ endfor %} - return s, nil -} -{% endmacro root_load %} +{% endmacro table_methods %} {% macro table_save(ctx) %} @@ -321,7 +103,7 @@ func Save{{ ctx.s.name }}Collection(ctx context.Context, dbe metadb.Execer, recs } var builder strings.Builder - builder.Grow({{ 50 + ctx.table_name|length + ctx.insert_column_expr|length + ctx.insert_update_expr|length }}+len(recs)*{{ ctx.insert_values_expr|length + 2 }}-1) + builder.Grow({{ 50 + ctx.table_name|length + ctx.insert_column_expr|length + ctx.insert_update_expr|length }}+len(recs)*{{ ctx.insert_values_expr|length + 3 }}-1) values := make([]any, 0, len(recs)*{{ ctx.insert_fields|length }}) @@ -340,9 +122,123 @@ func Save{{ ctx.s.name }}Collection(ctx context.Context, dbe metadb.Execer, recs } builder.WriteString(" ON DUPLICATE KEY UPDATE {{ ctx.insert_update_expr }}") + fmt.Println(builder.String()) + _, saveErr := dbe.ExecContext(ctx, builder.String(), values...) return errors.WithStack(saveErr) } + +func Save{{ ctx.s.name }}Diff(ctx context.Context, dbe metadb.Execer, rec {{ ctx.s.name }}) error { + if rec.changedFields.Empty() { + return nil + } + + var queryBuilder strings.Builder + queryBuilder.Grow({{ 52 + ctx.table_name|length + ctx.insert_column_expr|length + ctx.insert_values_expr|length + ctx.insert_update_expr|length }}) + queryBuilder.WriteString("INSERT INTO `{{ ctx.table_name }}` ({{ ctx.pkey_column_expr }}") + + var valuesBuilder strings.Builder + valuesBuilder.Grow({{ 10 + ctx.insert_values_expr|length }}) + valuesBuilder.WriteString(" VALUES ({{ ctx.pkey_values_expr }}") + + var updateBuilder strings.Builder + updateBuilder.Grow({{ 25 + ctx.insert_update_expr|length }}) + updateBuilder.WriteString(" ON DUPLICATE KEY UPDATE ") + + values := make([]any, 0, 10) + {% for f in ctx.pkey %} + values = append(values, rec.{{ f|fname }}) + {% endfor %} + + initUpdateLen := updateBuilder.Len() + + {% for f in ctx.raw_nonpk_fields %} + if rec.{{ f|fname }}Changed() { + queryBuilder.WriteString(",`{{ f.name }}`") + valuesBuilder.WriteString(",?") + + if updateBuilder.Len() > initUpdateLen { + updateBuilder.WriteRune(',') + } + updateBuilder.WriteString("`{{ f.name }}`=VALUES(`{{ f.name }}`)") + + values = append(values, rec.{{ f|fname }}) + } + {% endfor %} + + {% if has_token(ctx.s, 'table_json_kv') %} + var jsonBuilder strings.Builder + jsonBuilder.Grow(255) + {% for f in ctx.json_fields %} + {% set fname = f|fname %} + if rec.{{ f|fname }}Changed() { + {%~ if f.type is builtin %} + jsonBuilder.WriteString(",'$.{{ f|alias }}',?") + values = append(values, rec.{{ f|fname }}) + {%~ else %} + jsonBuilder.WriteString(",'$.{{ f|alias }}',") + jsonBytes, err := json.Marshal(rec.{{ fname }}) + if err != nil { + return errors.WithStack(err) + } + jsonBuilder.WriteString("CAST(") + jsonBuilder.WriteString(strconv.Quote(string(jsonBytes))) + jsonBuilder.WriteString(" AS JSON)") + {# {%~ if f.type is string %} + jsonBuilder.WriteString(strconv.Quote(rec.{{ fname }})) + {%~ elseif f.type is bool %} + jsonBuilder.WriteString(strconv.FormatBool(rec.{{ fname }})) + {%~ elseif f.type is uint %} + jsonBuilder.WriteString(strconv.FormatUint(uint64(rec.{{ fname }}), 10)) + {%~ elseif f.type is int %} + jsonBuilder.WriteString(strconv.FormatInt(int64(rec.{{ fname }}), 10)) + {%~ else %} + jsonBytes, err := json.Marshal(rec.{{ fname }}) + if err != nil { + return errors.WithStack(err) + } + jsonBuilder.WriteString("CAST(") + jsonBuilder.WriteString(strconv.Quote(string(jsonBytes))) + jsonBuilder.WriteString(" AS JSON)") #} + {%~ endif %} + } + {% endfor %} + + if jsonBuilder.Len() > 0 { + queryBuilder.WriteString(",`kv`") + valuesBuilder.WriteString(",?") + + if updateBuilder.Len() > initUpdateLen { + updateBuilder.WriteRune(',') + } + updateBuilder.WriteString("`kv`=JSON_SET(`kv`") + updateBuilder.WriteString(jsonBuilder.String()) + updateBuilder.WriteRune(')') + + values = append(values, rec.{{ ctx.s.name }}JsonValues) + } + {% endif %} + + valuesBuilder.WriteRune(')') + + queryBuilder.WriteRune(')') + queryBuilder.WriteString(valuesBuilder.String()) + queryBuilder.WriteString(updateBuilder.String()) + + fmt.Println(queryBuilder.String()) + + _, saveErr := dbe.ExecContext(ctx, queryBuilder.String(), values...) + return errors.Wrap(saveErr, queryBuilder.String()) +} + +func Save{{ ctx.s.name }}CollectionDiff(ctx context.Context, dbe metadb.Execer, recs []{{ ctx.s.name }}) error { + for _, rec := range recs { + if err := Save{{ ctx.s.name }}Diff(ctx, dbe, rec); err != nil { + return err + } + } + return nil +} {% endmacro table_save %} @@ -455,22 +351,7 @@ func Delete{{ ctx.s.name }}Collection( return errors.WithStack(err) } {% endif %} -{% endmacro table_delete %} - -{% macro table_save_coll_diff(struct_name) %} -func Save{{ struct_name }}CollectionDiff(ctx context.Context, dbe metadb.Execer, recs []{{ struct_name }}) error { - for _, rec := range recs { - if err := Save{{ struct_name }}Diff(ctx, dbe, rec); err != nil { - return err - } - } - return nil -} -{% endmacro table_save_coll_diff %} - - -{% macro table_delete_diff(ctx) %} func Delete{{ ctx.s.name }}ById( ctx context.Context, dbe metadb.Execer, @@ -527,12 +408,11 @@ func Delete{{ ctx.s.name }}CollectionById( _, delErr := dbe.ExecContext(ctx, builder.String(), values...) return errors.WithStack(delErr) } -{% endmacro table_delete_diff %} +{% endmacro table_delete %} -{% macro changed_fields(type_name, fields) %} +{% macro changed_fields_methods(type_name, fields) %} {% for f in fields %} - func (s {{ type_name }}) {{ f|fname }}Changed() bool { return s.changedFields.Changed("{{ f|alias }}") } @@ -541,7 +421,7 @@ func (s *{{ type_name }}) Set{{ f|fname }}Changed() { s.changedFields.SetChanged("{{ f|alias }}") } {% endfor %} -{% endmacro changed_fields %} +{% endmacro changed_fields_methods %} {% macro json_sql_impl(type_name) %} @@ -642,7 +522,7 @@ func (s *{{ name|default(o.name) }}) Reset() { {% endmacro reset_method %} -{% macro meta_read(s, name) %} +{% macro read_methods(s, name) %} {% set fields_len = s.fields|length %} func (s *{{ name|default(s.name) }}) Read(reader meta.Reader) error { @@ -702,7 +582,7 @@ func (s *{{ name|default(s.name) }}) ReadFields(reader meta.Reader) error { return nil } -{% endmacro meta_read %} +{% endmacro read_methods %} {% macro meta_read_field(type, fname, alias, tokens, has_table_token) %} @@ -785,7 +665,7 @@ func (s *{{ name|default(s.name) }}) ReadFields(reader meta.Reader) error { {% endmacro meta_read_field %} -{% macro meta_write(o, name) %} +{% macro write_methods(o, name) %} func (s *{{ name|default(o.name) }}) Write(writer meta.Writer) error { if err := writer.BeginContainer({{ get_all_fields(o)|length }}, ""); err != nil { return err @@ -813,7 +693,7 @@ func (s *{{ name|default(o.name) }}) WriteFields(writer meta.Writer) error { func (s *{{ name|default(o.name) }}) FieldsCount() int { return {{ get_all_fields(o)|length }} } -{% endmacro meta_write %} +{% endmacro write_methods %} {% macro meta_write_field(type, fname, alias, tokens) %} @@ -857,55 +737,7 @@ func (s *{{ name|default(o.name) }}) FieldsCount() int { {% endmacro meta_write_field %} -{% macro struct_diff_removed(s) %} -type {{ s.name }} struct { - {% for f in s.parent.fields %} - {{ f|fname }} {{ f.type|go_type }}Id `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` - {% endfor %} -} -{% endmacro struct_diff_removed %} - - -{% macro struct_identifier(name, pkey) %} -type {{ name }}Id struct { - {% for f in pkey %} - {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` - {% endfor %} -} -{% endmacro struct_identifier %} - - -{% macro struct_common(s) %} -type {{ s.name }} struct { - {% if s.parent %} - {{ s.parent.name }} - {% endif %} - - {% for f in s.fields %} - {{ f|fname }} {{ f.type|go_type(f.tokens) }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` - {% endfor %} - - {% if has_token(s, 'bitfields') %} - fieldsMask meta.FieldsMask - {% endif %} - - {% if has_token(s, 'table') %} - changedFields meta.ChangedFields - {% endif %} -} - -{{ _self.struct_methods(s.name, s.classid) }} -{{ _self.reset_method(s) }} -{{ _self.meta_read(s) }} -{{ _self.meta_write(s) }} - -{% if has_token(s, 'bitfields') %} -{{ _self.bitfields_methods(s.name) }} -{% endif %} -{% endmacro struct_common %} - - -{% macro struct_methods(name, classid) %} +{% macro common_methods(name, classid) %} func New{{ name }}() *{{ name }} { s := new({{ name }}) s.Reset() @@ -940,7 +772,7 @@ func Ptr{{ name }}(s meta.Struct) *{{ name }} { } return ptr.Ptr{{ name }}() } -{% endmacro struct_methods %} +{% endmacro common_methods %} {% macro bitfields_methods(name) %} @@ -960,3 +792,125 @@ func (s *{{ name }}) FieldsMask() meta.FieldsMask { return s.fieldsMask } {% endmacro bitfields_methods %} + + +{% macro data_root_methods(s) %} +{% set root_field = meta_field(token(s, 'data_root')) %} +{% set fields = s.fields|filter(f => not has_token(f, 'db_skip')) %} + +{{ _self.root_set_pk(_context) }} +{{ _self.root_save(_context) }} +{{ _self.root_load(_context) }} +{{ _self.root_delete(_context) }} +{% endmacro %} + + +{% macro root_set_pk(ctx) %} +{% set s = ctx.s %} +{% set set_field = ctx.root_field %} +func (s *{{ s.name }}) Set{{ set_field|fname }}({{ set_field|varname }} {{ set_field.type|go_type }}) { + {%~ for f in ctx.fields %} + {%~ if f.type is array %} + {% set pkey = table_pkey(f.type.value) %} + {% set field_name = pkey|first|fname %} + + for i := range s.{{ f|fname }} { + s.{{ f|fname }}[i].{{ field_name }} = {{ set_field|varname }} + } + {%~ else %} + {% set pkey = table_pkey(f.type) %} + {% set field_name = pkey|first|fname %} + + s.{{ f|fname }}.{{ field_name }} = {{ set_field|varname }} + {%~ endif %} + {%~ endfor %} +} +{% endmacro root_set_pk %} + + +{% macro root_save(ctx) %} +{% set s = ctx.s %} +func Save{{ s.name }}(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { + {%~ for f in ctx.fields %} + {%~ if f.type is array %} + if err := Save{{ f.type.value.name }}Collection(ctx, dbe, rec.{{ f|fname }}); err != nil { + return err + } + {%~ else %} + if err := Save{{ f.type.name }}(ctx, dbe, rec.{{ f|fname }}); err != nil { + return err + } + {%~ endif %} + {%~ endfor %} + return nil +} + +func Save{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { + {%~ for f in ctx.fields %} + {%~ if f.type is array %} + if err := Save{{ f.type.value.name }}CollectionDiff(ctx, dbe, rec.{{ f|fname }}); err != nil { + return err + } + {%~ else %} + if err := Save{{ f.type.name }}Diff(ctx, dbe, rec.{{ f|fname }}); err != nil { + return err + } + {%~ endif %} + {%~ endfor %} + return nil +} +{% endmacro root_save %} + + +{% macro root_load(ctx) %} +{% set s = ctx.s %} +{% set load_by = ctx.root_field %} +func Load{{ s.name }}(ctx context.Context, dbq metadb.Querier, {{ load_by|varname }} {{ load_by.type|go_type }}) ({{ s.name }}, error) { + var s {{ s.name }} + {%~ for f in ctx.fields %} + {%~ if f.type is array %} + {{ f|varname }}, err := Load{{ f.type.value.name }}Collection(ctx, dbq, {{ load_by|varname }}) + {%~ else %} + {{ f|varname }}, err := Load{{ f.type.name }}(ctx, dbq, {{ load_by|varname }}) + {%~ endif %} + if err != nil { + return {{ s.name }}{}, err + } + s.{{ f|fname }} = {{ f|varname }} + {%~ endfor %} + return s, nil +} +{% endmacro root_load %} + + +{% macro root_delete(ctx) %} +{% set s = ctx.s %} +{% set delete_by = ctx.root_field %} +func Delete{{ s.name }}(ctx context.Context, dbe metadb.Execer, {{ delete_by|varname }} {{ delete_by.type|go_type }}) error { + {%~ for f in ctx.fields %} + {%~ if f.type is array %} + if err := Delete{{ f.type.value.name }}Collection(ctx, dbe, {{ delete_by|varname }}); err != nil { + return err + } + {%~ elseif f.type is struct %} + if _, err := Delete{{ f.type.name }}(ctx, dbe, {{ delete_by|varname }}); err != nil { + return err + } + {%~ else %} + if err := Delete{{ f.type.name }}(ctx, dbe, {{ delete_by|varname }}); err != nil { + return err + } + {%~ endif %} + {%~ endfor %} + return nil +} + +func Delete{{ s.name }}Diff(ctx context.Context, dbe metadb.Execer, ids {{ s.name }}RemovedIds) error { + {%~ for f in ctx.fields|filter(f => f.type is array) %} + if err := Delete{{ f.type.value.name }}CollectionById(ctx, dbe, ids.{{ f|fname }}); err != nil { + return err + } + {%~ endfor %} + return nil +} +{% endmacro root_delete %}