From c1128ec7c41dcb1b4dfe1e36c022114ee687f2bf Mon Sep 17 00:00:00 2001 From: Pavel Merzlyakov Date: Tue, 6 Jun 2023 14:37:34 +0300 Subject: [PATCH] generating sql methods, reading/writing associative containers --- src/codegen.inc.php | 402 +++++++++++---- tpl/codegen_bundle.twig | 108 ++-- tpl/macros_enum.twig | 59 +++ tpl/macros_rpc.twig | 70 +++ tpl/macros_struct.twig | 1048 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 1557 insertions(+), 130 deletions(-) create mode 100644 tpl/macros_enum.twig create mode 100644 tpl/macros_rpc.twig create mode 100644 tpl/macros_struct.twig diff --git a/src/codegen.inc.php b/src/codegen.inc.php index 4b7527c..37ed133 100644 --- a/src/codegen.inc.php +++ b/src/codegen.inc.php @@ -1,6 +1,18 @@ true, - 'autoescape' => false, - 'strict_variables' => true] - ); + 'debug' => true, + 'autoescape' => false, + 'strict_variables' => true, + ]); $twig->addExtension(new \Twig\Extension\DebugExtension()); - _add_twig_support($twig); + addTwigTests($twig); + addTwigFilters($twig); + addTwigFunctions($twig); return $twig; } @@ -22,117 +36,317 @@ function get_twig(array $inc_path = []) function supported_tokens() { return [ - 'POD', + 'table', + 'table_pkey', + 'table_json_kv', + + 'root', + 'bitfields', + 'diffable', + 'diff_removed', + + 'alias', 'default', 'optional', - 'bitfields', - 'cloneable', 'virtual', - 'table', - 'id', - 'owner', - 'pkey', - 'statist', 'statist_skip', 'statist_alias', ]; } -function _add_twig_support(\Twig\Environment $twig) +function addTwigTests(\Twig\Environment $twig) { - $twig->addTest(new \Twig\TwigTest('instanceof', - function($obj, $class) - { - return (new \ReflectionClass($class))->isInstance($obj); + $twig->addTest(new TwigTest( + 'metaenum', + fn(mtgMetaInfoUnit $u): bool => $u->object instanceof mtgMetaEnum, + )); + $twig->addTest(new TwigTest( + 'metastruct', + fn(mtgMetaInfoUnit $u): bool => $u->object instanceof mtgMetaStruct, + )); + $twig->addTest(new TwigTest( + 'metarpc', + fn(mtgMetaInfoUnit $u): bool => $u->object instanceof mtgMetaRPC, + )); + $twig->addTest(new TwigTest( + 'enum', + fn(mtgType $t): bool => $t instanceof mtgMetaEnum, + )); + $twig->addTest(new TwigTest( + 'struct', + fn(mtgType $t): bool => $t instanceof mtgMetaStruct, + )); + $twig->addTest(new TwigTest( + 'array', + fn(mtgType $t): bool => $t instanceof mtgArrType, + )); + $twig->addTest(new TwigTest( + 'builtin', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType, + )); + $twig->addTest(new TwigTest( + 'numeric', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isNumeric(), + )); + $twig->addTest(new TwigTest( + 'int', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isInt(), + )); + $twig->addTest(new TwigTest( + 'uint', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isUint(), + )); + $twig->addTest(new TwigTest( + 'bool', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isBool(), + )); + $twig->addTest(new TwigTest( + 'float', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isFloat(), + )); + $twig->addTest(new TwigTest( + 'double', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isDouble(), + )); + $twig->addTest(new TwigTest( + 'string', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isString(), + )); + $twig->addTest(new TwigTest( + 'blob', + fn(mtgType $t): bool => $t instanceof mtgBuiltinType && $t->isBlob(), + )); + + $twig->addTest(new TwigTest( + 'instanceof', + fn($obj, $class): bool => (new \ReflectionClass($class))->isInstance($obj), + )); +} + +function addTwigFilters(\Twig\Environment $twig) +{ + $twig->addFilter(new TwigFilter( + 'go_type', + fn(mtgType $t): string => goType($t), + )); + $twig->addFilter(new TwigFilter( + 'ucfirst', + fn(string $str): string => ucfirst($str), + )); + $twig->addFilter(new TwigFilter( + 'lcfirst', + fn(string $str): string => lcfirst($str), + )); + $twig->addFilter(new TwigFilter( + 'camel', + fn(string $str): string => snake2camel($str), + )); + $twig->addFilter(new TwigFilter( + 'quote', + fn(string $str, string $char = '`'): string => "{$char}{$str}{$char}" + )); + $twig->addFilter(new TwigFilter( + 'sql_where', + function(array $fields): string { + $exprs = []; + foreach ($fields as $field) { + $fieldName = $field->getName(); + $exprs[] = "`{$fieldName}` = ?"; + } + return implode(' AND ', $exprs); } )); - $twig->addFunction(new \Twig\TwigFunction('Error', - function($e) - { + $twig->addFilter(new TwigFilter( + 'alias', + function(mtgMetaField $field): string { + if ($field->hasToken('alias')) { + return $field->getToken('alias'); + } + return $field->getName(); + } + )); + $twig->addFilter(new TwigFilter( + 'fname', + fn(mtgMetaField $field): string => snake2camel($field->getName()) + )); + $twig->addFilter(new TwigFilter( + 'varname', + fn(mtgMetaField $field): string => lcfirst(snake2camel($field->getName())) + )); + $twig->addFilter(new TwigFilter( + 'builtin_type_suffix', + fn (mtgBuiltinType $type): string => builtinTypeMethodSuffix($type), + )); + $twig->addFilter(new TwigFilter( + 'default_val', + fn(mtgMetaField $field) => defaultValue($field) + )); + + $twig->addFilter(new TwigFilter( + 'rpc_invert', + fn(string $name): string => rpc_invert($name), + )); +} + +function addTwigFunctions(\Twig\Environment $twig) +{ + $twig->addFunction(new TwigFunction( + 'Error', + function(string $e) { throw new Exception($e); } )); - $twig->addFunction(new \Twig\TwigFunction('has_token', - function($o, $token) - { - return $o->hasToken($token); - } + $twig->addFunction(new TwigFunction( + 'has_token', + fn($o, string $token): bool => $o->hasToken($token), )); - $twig->addFunction(new \Twig\TwigFunction('has_token_in_parent', - function($o, $token) - { - return $o->hasTokenInParent($token); - } + $twig->addFunction(new TwigFunction( + 'has_token_in_parent', + fn(mtgMetaStruct $s, string $token): bool => $s->hasTokenInParent($token), )); - $twig->addFunction(new \Twig\TwigFunction('token', - function($o, $name) - { - return $o->getToken($name); - } + $twig->addFunction(new TwigFunction( + 'token', + fn($o, string $name) => $o->getToken($name), )); - $twig->addFunction(new \Twig\TwigFunction('token_or', - function($o, $name, $v) - { - if($o->hasToken($name)) - return $o->getToken($name); - else - return $v; - } + $twig->addFunction(new TwigFunction( + 'token_or', + fn($o, string $name, $v) => $o->hasToken($name) ? $o->getToken($name) : $v, )); - $twig->addFunction(new \Twig\TwigFunction('get_all_fields', - function($o) - { - return \mtg_get_all_fields($o); - } + $twig->addFunction(new TwigFunction( + 'get_all_fields', + fn(mtgMetaStruct $s): array => \mtg_get_all_fields($s), )); - $twig->addFunction(new \Twig\TwigFunction('count_optional', - function($os) - { + $twig->addFunction(new TwigFunction( + 'count_optional', + function($os) { $opts = 0; foreach($os as $o) if($o->hasToken('optional')) ++$opts; return $opts; } + )); + $twig->addFunction(new TwigFunction( + 'arr_fill', + fn($value, int $count): array => array_fill(0, $count, $value) )); - $twig->addFunction(new \Twig\TwigFunction('field_reset', + $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; + } + )); + $twig->addFunction(new TwigFunction( + 'table_fields', + function(mtgMetaStruct $struct): array { + $pkey = explode(',', $struct->getToken('table_pkey')); + return array_filter( + $struct->getFields(), + fn(mtgMetaField $field): bool => !in_array($field->getName(), $pkey), + ); + } + )); + $twig->addFunction(new TwigFunction( + 'meta_field', + function (string $str) use ($twig): mtgMetaField { + $meta = $twig->getGlobals()['meta']; + return parseMetaField($str, $meta); + } + )); + + $twig->addFunction(new TwigFunction('field_reset', function($name, $type, $tokens) { return field_reset($name, $type, $tokens); } )); - $twig->addFunction(new \Twig\TwigFunction('buf2var', + $twig->addFunction(new TwigFunction('buf2var', function($name, $fname, $type, $buf, $tokens, $is_ptr) { return buf2var($name, $fname, $type, $buf, $tokens, $is_ptr); } )); - $twig->addFunction(new \Twig\TwigFunction('var2buf', + $twig->addFunction(new TwigFunction('var2buf', function($name, $fname, $type, $buf, $tokens, $is_ptr) { return var2buf($name, $fname, $type, $buf, $tokens, $is_ptr); } )); - $twig->addFilter(new \Twig\TwigFilter('rpc_invert', - function($name) - { - return rpc_invert($name); +} + +function snake2camel(string $str): string +{ + return str_replace('_', '', ucwords($str, '_')); +} + +function goType(mtgType $type, array $tokens = []): string +{ + if ($type instanceof mtgMetaEnum) { + return $type->getName(); + } else if ($type instanceof mtgMetaStruct) { + return $type->getName(); + } else if ($type instanceof mtgBuiltinType) { + if ($type->isFloat()) { + return 'float32'; + } else if ($type->isDouble()) { + return 'float64'; + } else if ($type->isBlob()) { + return '[]byte'; + } else { + return $type->getName(); } - )); - $twig->addFilter(new \Twig\TwigFilter('go_type', - function($type, $tokens) - { - return go_type($type, $tokens); + } else if ($type instanceof mtgArrType) { + $native = goType($type->getValue()); + return "[]$native"; + } else { + throw new Exception("Unknown type '$type'"); + } +} + +function builtinTypeMethodSuffix(mtgBuiltinType $type): string +{ + if ($type->isBlob()) { + return 'Bytes'; + } + return ucfirst(goType($type)); +} + +function defaultValue(mtgMetaField $field) +{ + $type = $field->getType(); + $tokens = $field->getTokens(); + $default = $field->getToken('default'); + + if ($type instanceof mtgBuiltinType) { + if ($type->isNumeric()) { + return $default ?? 0; + } else if ($type->isString()) { + return $default ?? '""'; + } else if($type->isBool()) { + return $default ?? 'false'; + } else if($type->isBlob()) { + return $default ?? 'nil'; + } else { + throw new Exception("Unknown type '$type'"); } - )); - $twig->addFilter(new \Twig\TwigFilter('ucfirst', - function($str) - { - return ucfirst($str); - } - )); + } else if($type instanceof mtgMetaEnum) { + return $default ? goType($type, $tokens)."_".trim($tokens['default'], '"') : 0; + } else { + throw new Exception("Unknown type '$type'"); + } +} + +function parseMetaField(string $str, mtgMetaInfo $meta): mtgMetaField +{ + list($name, $type) = explode('|', $str); + return new mtgMetaField($name, new mtgTypeRef(new mtgBuiltinType($type), $meta)); } function go_type(\mtgType $type, array $tokens = array()) @@ -177,7 +391,7 @@ function go_type(\mtgType $type, array $tokens = array()) throw new Exception("Unknown type '$type'"); } -function builtin_type_prefix(\mtgBuiltinType $type) +function builtin_type_prefix(mtgBuiltinType $type) { switch($type->getName()) { @@ -214,11 +428,11 @@ function builtin_type_prefix(\mtgBuiltinType $type) } } -function field_reset($name, \mtgType $type, array $tokens) +function field_reset($name, mtgType $type, array $tokens) { $str = ''; - if($type instanceof \mtgBuiltinType) + if($type instanceof mtgBuiltinType) { if($type->isNumeric()) { @@ -251,23 +465,23 @@ function field_reset($name, \mtgType $type, array $tokens) else throw new Exception("Unknown type '$type'"); } - else if($type instanceof \mtgArrType) + else if($type instanceof mtgArrType) { - $str = "if self.$name == nil { \nself.$name = make(".go_type($type, $tokens).",0) \n}\n "; + $str = "if self.$name == nil { \nself.$name = make(".goType($type, $tokens).",0) \n}\n "; $str .= "self.$name = self.{$name}[0:0]"; } - else if($type instanceof \mtgMetaEnum) + else if($type instanceof mtgMetaEnum) { if(array_key_exists('default', $tokens)) - $str = "self.$name = ".go_type($type, $tokens)."_".trim($tokens['default'], '"'); + $str = "self.$name = ".goType($type, $tokens)."_".trim($tokens['default'], '"'); else $str = "self.$name = 0"; } - else if($type instanceof \mtgMetaStruct) + else if($type instanceof mtgMetaStruct) { $is_virtual = array_key_exists("virtual", $tokens); if($is_virtual) - $str .= "self.$name = New".ltrim(go_type($type, $tokens),'I')."() "; + $str .= "self.$name = New".ltrim(goType($type, $tokens),'I')."() "; else $str .= "self.$name.Reset()"; } @@ -276,30 +490,30 @@ function field_reset($name, \mtgType $type, array $tokens) return $str; } -function buf2var($name, $fname, \mtgType $type, $buf, array $tokens = array(), $is_ptr = false) +function buf2var($name, $fname, mtgType $type, $buf, array $tokens = array(), $is_ptr = false) { $str = ''; - if($type instanceof \mtgBuiltinType) + if($type instanceof mtgBuiltinType) { $str .= _read_op($tokens, $buf.'.Read'.builtin_type_prefix($type).'(&'.$fname.', "'.$name.'")'); } - else if($type instanceof \mtgMetaEnum) + else if($type instanceof mtgMetaEnum) { $str .= _read_op($tokens, "{$buf}.ReadI32((*int32)(&{$fname}), \"$name\")"); $str .= "\n if !{$fname}.IsValid() { return errors.Errorf(\"Bad enum value %d for $name\", $fname) }"; } - else if($type instanceof \mtgMetaStruct) + else if($type instanceof mtgMetaStruct) { if(array_key_exists('virtual', $tokens)) $str .= "if v, err := meta.ReadStructGeneric($buf, CreateById, \"{$name}\"); err != nil { return err } else { {$fname} = v.(I{$type}) }"; else $str .= _read_op($tokens, "meta.ReadStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")"); } - else if($type instanceof \mtgArrType) + else if($type instanceof mtgArrType) { $is_virtual = array_key_exists("virtual", $tokens); - $native_type = go_type($type->getValue(), $tokens); + $native_type = goType($type->getValue(), $tokens); $offset = "\n "; $str .= "/*[]{$name}*/"; $str .= $offset . _read_op($tokens, "{$buf}.BeginContainer(\"$name\")"); @@ -308,14 +522,14 @@ function buf2var($name, $fname, \mtgType $type, $buf, array $tokens = array(), $ $str .= $offset . "_{$name}_size, err := {$buf}.GetContainerSize()"; $str .= $offset . "if err != nil { return err }"; - $need_new = !$is_virtual && $type->getValue() instanceof \mtgMetaStruct; + $need_new = !$is_virtual && $type->getValue() instanceof mtgMetaStruct; $str .= $offset . "for ; _{$name}_size > 0; _{$name}_size-- {"; $offset = "\n "; $str .= $offset . "var tmp_{$name} {$native_type}"; $str .= $offset . buf2var("", "tmp_{$name}", $type->getValue(), $buf, $tokens, $is_virtual); - $str .= $offset . "{$fname} = append({$fname}, ".($need_new?"&":"")."tmp_{$name})"; + $str .= $offset . "{$fname} = append({$fname}, tmp_{$name})"; $offset = "\n "; $str .= $offset . "}"; @@ -328,26 +542,26 @@ function buf2var($name, $fname, \mtgType $type, $buf, array $tokens = array(), $ return $str; } -function var2buf($name, $fname, \mtgType $type, $buf, array $tokens = array(), $is_ptr = false) +function var2buf($name, $fname, mtgType $type, $buf, array $tokens = array(), $is_ptr = false) { $str = ''; - if($type instanceof \mtgBuiltinType) + if($type instanceof mtgBuiltinType) { $str .= _write_op("{$buf}.Write".builtin_type_prefix($type)."($fname, \"$name\")")."\n"; } - else if($type instanceof \mtgMetaEnum) + else if($type instanceof mtgMetaEnum) { $str .= _write_op("{$buf}.WriteI32(int32($fname), \"$name\")"); } - else if($type instanceof \mtgMetaStruct) + else if($type instanceof mtgMetaStruct) { if(array_key_exists('virtual', $tokens)) $str .= _write_op("meta.WriteStructGeneric($buf, ".($is_ptr?"":"&")."$fname, \"$name\")"); else - $str .= _write_op("meta.WriteStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")"); + $str .= _write_op("meta.WriteStruct($buf, &$fname, \"$name\")"); } - else if($type instanceof \mtgArrType) + else if($type instanceof mtgArrType) { $str .= "{$buf}.BeginContainer(\"{$name}\")\n"; $str .= " for _, v := range({$fname}) {\n"; diff --git a/tpl/codegen_bundle.twig b/tpl/codegen_bundle.twig index 0314dda..069a09c 100644 --- a/tpl/codegen_bundle.twig +++ b/tpl/codegen_bundle.twig @@ -1,54 +1,90 @@ -package {{package}} +package {{ package }} //THIS FILE IS GENERATED AUTOMATICALLY, DO NOT TOUCH IT! import ( - "fmt" - "sort" - "github.com/pkg/errors" - "git.bit5.ru/backend/meta" + "context" + "database/sql" + "sort" + + "git.bit5.ru/backend/meta" + "git.bit5.ru/gomodules/metadb" + "github.com/doug-martin/goqu/v9" + "github.com/doug-martin/goqu/v9/exp" + "github.com/pkg/errors" ) -//supress *imported but not used* warnings -var _ = fmt.Printf -var _ = sort.SearchInts +{% import "macros_enum.twig" as enum_macros %} +{% import "macros_struct.twig" as struct_macros %} +{% import "macros_rpc.twig" as rpc_macros %} -{%- import "macro.twig" as macro -%} +{% set enums = meta.getunits|filter(u => u is metaenum) %} +{% set structs = meta.getunits|filter(u => u is metastruct) %} +{% set rpcs = meta.getunits|filter(u => u is metarpc) %} -{{ macro.decl_units(meta) }} +{% for u in enums %} +{{ enum_macros.enum(u.object) }} +{% endfor %} -func CreateById(classId uint32) (meta.IMetaStruct, error) { +{% for u in structs %} +{{ struct_macros.struct(u.object) }} +{% endfor %} + +func CreateById(classId uint32) (meta.Struct, error) { switch classId { - -{%- for u in meta.getunits ~%} -{%- if u.object is instanceof('\\mtgMetaStruct')~%} - case {{u.object.classid}}: { return New{{u.object.name}}(), nil } -{%- endif ~%} -{%- endfor ~%} - - default : return nil, errors.Errorf("Can't find struct for class id %d", classId) + {% for u in structs %} + case {{ u.object.classid }}: + return New{{ u.object.name }}(), nil + {% endfor %} + default: + return nil, errors.Errorf("Can't find struct for class id %d", classId) } } -func CreateByName(name string) (meta.IMetaStruct, error) { + +func CreateByName(name string) (meta.Struct, error) { switch name { - -{%- for u in meta.getunits ~%} -{%- if u.object is instanceof('\\mtgMetaStruct')~%} - case "{{u.object.name}}": { return New{{u.object.name}}(), nil } -{%- endif ~%} -{%- endfor ~%} - - default : return nil, errors.Errorf("Can't find struct for name %s", name) + {% for u in structs %} + case "{{ u.object.name }}": + return New{{ u.object.name }}(), nil + {% endfor %} + default: + return nil, errors.Errorf("Can't find struct for name %s", name) } } -func CreateRPC(code uint32)(meta.IRPC, error) { + +{% for u in rpcs %} +{{ rpc_macros.rpc(u.object) }} +{% endfor %} + +func CreateRPC(code uint32) (Rpc, error) { switch code { - -{%- for u in meta.getunits ~%} -{%- if u.object is instanceof('\\mtgMetaRPC')~%} - case {{u.object.code}}: { return New{{u.object.name}}(), nil } -{%- endif ~%} -{%- endfor ~%} - default: return nil, errors.Errorf("Can't find rpc for code %d", code) + {%~ for u in rpcs %} + {%- set name = u.object.name|lower|camel|replace({'Rpc': ''}) ~ 'Rpc' %} + case {{ u.object.code }}: + return &{{ name }}{}, nil + {%~ endfor %} + default: + return nil, errors.Errorf("Can't find rpc for code %d", code) } } +type RpcHandler interface { + {%~ for u in rpcs %} + {%- set name = u.object.name|lower|camel|replace({'Rpc': ''}) %} + {{ name }}({{ name ~ 'Rpc' }}) error + {%~ endfor %} +} + +type Rpc interface { + Code() uint32 + Name() string + + Error() (int32, string) + SetError(int32, string) + + Execute(RpcHandler) error + + ReadRequest(meta.Reader) error + WriteResponse(meta.Writer) error +} + +type CreateRpcFunc func(uint32) (Rpc, error) diff --git a/tpl/macros_enum.twig b/tpl/macros_enum.twig new file mode 100644 index 0000000..3f0847b --- /dev/null +++ b/tpl/macros_enum.twig @@ -0,0 +1,59 @@ +{% macro enum(o) %} +{% set values_var = '_' ~ o.name ~ '_values' %} +{% set map_var = '_' ~ o.name ~ '_map' %} + +type {{o.name}} int32 + +const ( + {%~ for k,v in o.values %} + {{o.name}}_{{k}} {{o.name}} = {{v}} + {%~ endfor %} +) + +var {{ values_var }} = []int{ + {%- for v in o.values|sort -%} + {{v}}, + {%- endfor -%} +} + +var {{ map_var }} map[string]{{o.name}} +func init() { + {{ map_var }} = map[string]{{o.name}}{ + {%- for k,v in o.values -%} + "{{k}}": {{v}}, + {%- endfor -%} + } +} + +func (*{{o.name}}) CLASS_ID() uint32 { + return {{o.classid}} +} + +func (*{{o.name}}) CLASS_NAME() string { + return "{{o.name}}" +} + +func (*{{o.name}}) DEFAULT_VALUE() int32 { + return {{o.values|first}} +} + +func (enum *{{o.name}}) IsValid() bool { + return sort.SearchInts({{ values_var }}, int(*enum)) != -1 +} + +func {{o.name}}_GetNameByValue(value int) string { + for name, num := range {{ map_var }} { + if value == int(num) { + return name + } + } + return "" +} + +func New{{o.name}}ByName(name string) ({{o.name}}, error) { + if v, ok := {{ map_var }}[name]; ok { + return v, nil + } + return 0, errors.Errorf("Wrong name of {{o.name}}: '%s'", name) +} +{% endmacro enum %} diff --git a/tpl/macros_rpc.twig b/tpl/macros_rpc.twig new file mode 100644 index 0000000..20df9d8 --- /dev/null +++ b/tpl/macros_rpc.twig @@ -0,0 +1,70 @@ +{% macro rpc(r) %} +{% import "macros_struct.twig" as struct_macros %} + +{% set prefix = r.name|lower|camel|replace({'Rpc': ''}) %} +{% set name = prefix ~ 'Rpc' %} +{% set req_name = prefix ~ 'Request' %} +{% set rsp_name = prefix ~ 'Response' %} + +type {{ name }} struct { + req {{ req_name }} + rsp {{ rsp_name }} + + errCode int32 + errMsg string +} + +func (rpc {{ name }}) Name() string { + return "{{ name }}" +} + +func (rpc {{ name }}) Code() uint32 { + return {{ r.code }} +} + +func (rpc *{{ name }}) SetError(code int32, msg string) { + rpc.errCode = code + rpc.errMsg = msg +} + +func (rpc {{ name }}) Error() (int32, string) { + return rpc.errCode, rpc.errMsg +} + +func (rpc {{ name }}) Execute(h RpcHandler) error { + return h.{{ prefix }}(rpc) +} + +func (rpc {{ name }}) Request() {{ req_name }} { + return rpc.req +} + +func (rpc *{{ name }}) SetResponse(rsp {{ rsp_name }}) { + rpc.rsp = rsp +} + +func (rpc *{{ name }}) ReadRequest(reader meta.Reader) error { + return rpc.req.Read(reader) +} + +func (rpc *{{ name }}) WriteResponse(writer meta.Writer) error { + return rpc.rsp.Write(writer) +} + +type {{ req_name }} struct { + {%~ for f in r.req.fields %} + {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` + {%~ endfor %} +} + +{{ struct_macros.struct_required_fields(req_name, r.req.fields)}} +{{ struct_macros.meta_read(r.req, req_name) }} + +type {{ rsp_name }} struct { + {%~ for f in r.rsp.fields %} + {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` + {%~ endfor %} +} + +{{ struct_macros.meta_write(r.rsp, rsp_name) }} +{% endmacro rpc %} diff --git a/tpl/macros_struct.twig b/tpl/macros_struct.twig new file mode 100644 index 0000000..8a9c5b6 --- /dev/null +++ b/tpl/macros_struct.twig @@ -0,0 +1,1048 @@ +{% 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, 'root') %} + {{ _self.root_save(s) }} + {{ _self.root_delete(s) }} + {{ _self.root_save_diff(s) }} + {{ _self.root_delete_diff(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 %} + +type {{ s.name }} struct { + {%~ for f in pkey %} + {{ f|fname }} {{ f.type|go_type }} `json:"{{ f|alias }}" msgpack:"{{ f|alias }}"` + {%~ endfor %} + + {{ 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 +} + +{{ _self.json_sql_impl(json_fields_type) }} + +{{ _self.struct_methods(s.name, s.classid) }} +{{ _self.struct_required_fields(s.name, s.fields) }} +{{ _self.meta_read(s) }} +{{ _self.meta_write(s) }} +{# {{ _self.struct_identifier(s.name, pkey) }} #} + +{{ _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 { + var b strings.Builder + if err := rec.WriteJsonSetExpr(&b); err != nil { + return err + } + + ds := metadb.SqlBuilder(). + Insert(goqu.T("{{ table_name }}")). + Cols( + {% for c in insert_columns -%} + goqu.C("{{ c }}"), + {% endfor -%} + ). + Vals(goqu.Vals{ + {% for f in insert_fields -%} + rec.{{ f }}, + {% endfor -%} + }). + OnConflict(goqu.DoUpdate("", + goqu.Record{"kv": goqu.L(b.String())}, + )) + + query, _, err := ds.ToSQL() + if err != nil { + return errors.WithStack(err) + } + + _, saveErr := dbe.ExecContext(ctx, query) + return errors.WithStack(saveErr) +} + +{{ _self.table_save_coll_diff(s.name) }} +{% endmacro struct_json_kv %} + + +{% macro struct_table(s) %} +{% set pkey = table_pkey(s) %} +{% set nopkey = table_fields(s) %} +{% 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_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 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 %} + +{{ _self.changed_fields(s.name, nopkey) }} + +{# {{ _self.struct_identifier(s.name, pkey) }} #} + +{{ _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 { + ds := metadb.SqlBuilder(). + Insert(goqu.T("{{ table_name }}")). + Cols( + {% for f in pkey %} + goqu.C("{{ f.name }}"), + {% endfor %} + ) + + vals := make(goqu.Vals, 0, {{ s.fields|length }}) + {% for f in pkey %} + vals = append(vals, rec.{{ f|fname }}) + {% endfor %} + + updateRecord := make(goqu.Record, {{ nopkey|length }}) + {% for f in nopkey %} + if rec.{{ f|fname }}Changed() { + ds = ds.ColsAppend(goqu.C("{{ f.name }}")) + vals = append(vals, rec.{{ f|fname }}) + updateRecord["{{ f.name }}"] = goqu.L("VALUES(`{{ f.name }}`)") + } + {% endfor %} + + ds = ds.Vals(vals).OnConflict(goqu.DoUpdate("", updateRecord)) + + query, _, err := ds.ToSQL() + if err != nil { + return errors.WithStack(err) + } + + _, saveErr := dbe.ExecContext(ctx, query) + return errors.WithStack(saveErr) +} + +{{ _self.table_save_coll_diff(s.name) }} +{% endmacro struct_table %} + + +{% macro root_save(s) %} +func Save{{ s.name }}(ctx context.Context, dbe metadb.Execer, rec {{ s.name }}) error { + {%~ for f in s.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 +} +{% 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 %} + {%~ 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, '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 %} + {%~ if f.type is array %} + if err := Delete{{ f.type.value.name }}Collection(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 %} + {%~ if f.type is array %} + if err := Delete{{ f.type.value.name }}CollectionById(ctx, dbe, ids.{{ f|fname }}); err != nil { + return err + } + {%~ else %} + if err := Delete{{ f.type.name }}ById(ctx, dbe, ids.{{ f|fname }}); err != nil { + return err + } + {%~ endif %} + {%~ endfor %} + return nil +} +{% endmacro root_delete_diff %} + + +{% macro table_save(ctx) %} +func Save{{ ctx.s.name }}(ctx context.Context, dbe metadb.Execer, rec {{ ctx.s.name }}) error { + query := "INSERT INTO `{{ ctx.table_name }}` ({{ ctx.insert_column_expr }}) VALUES ({{ ctx.insert_values_expr }}) ON DUPLICATE KEY UPDATE {{ ctx.insert_update_expr }}" + + _, saveErr := dbe.ExecContext( + ctx, + query, + {%~ for f in ctx.insert_fields %} + rec.{{ f }}, + {%~ endfor %} + ) + return errors.WithStack(saveErr) +} + +func Save{{ ctx.s.name }}Collection(ctx context.Context, dbe metadb.Execer, recs []{{ ctx.s.name }}) error { + if len(recs) == 0 { + return nil + } + + ds := metadb.SqlBuilder(). + Insert(goqu.T("{{ ctx.table_name }}")). + Cols( + {%~ for c in ctx.insert_columns %} + goqu.C("{{ c }}"), + {%~ endfor %} + ). + OnConflict(goqu.DoUpdate("", + goqu.Record{ + {%~ for c in ctx.update_columns %} + "{{ c }}": goqu.L("VALUES(`{{ c }}`)"), + {%~ endfor %} + }, + )) + + for _, rec := range recs { + ds = ds.Vals(goqu.Vals{ + {%~ for f in ctx.insert_fields %} + rec.{{ f }}, + {%~ endfor %} + }) + } + + query, _, err := ds.ToSQL() + if err != nil { + return errors.WithStack(err) + } + + _, saveErr := dbe.ExecContext(ctx, query) + return errors.WithStack(saveErr) +} +{% endmacro table_save %} + + +{% macro table_load(ctx) %} +func Load{{ ctx.s.name }}( + ctx context.Context, + dbq metadb.Querier, + {%~ for f in ctx.pkey %} + {{ f|varname }} {{ f.type|go_type }}, + {%~ endfor %} +) ({{ ctx.s.name }}, error) { + query := "SELECT {{ ctx.select_column_expr }} FROM `{{ ctx.table_name }}` WHERE {{ ctx.select_where_expr }}" + + row := dbq.QueryRowContext(ctx, query, + {%- for f in ctx.pkey -%} + {{ f|varname }}, + {%- endfor -%} + ) + + var rec {{ ctx.s.name }} + if err := row.Scan( + {%~ for f in ctx.select_fields %} + &rec.{{ f }}, + {%~ endfor %} + ); err != nil { + if err == sql.ErrNoRows { + return {{ ctx.s.name }}{}, nil + } + return {{ ctx.s.name }}{}, errors.WithStack(err) + } + + return rec, nil +} + +func Load{{ ctx.s.name }}Collection( + ctx context.Context, + dbq metadb.Querier, + {{ ctx.select_by|varname }} {{ ctx.select_by.type|go_type }}, +) ([]{{ ctx.s.name }}, error) { + query := "SELECT {{ ctx.select_column_expr }} FROM `{{ ctx.table_name }}` WHERE `{{ ctx.select_by.name }}` = ?" + + rows, err := dbq.QueryContext(ctx, query, {{ ctx.select_by|varname }}) + if err != nil { + return nil, errors.WithStack(err) + } + defer rows.Close() + + records := make([]{{ ctx.s.name }}, 0) + for rows.Next() { + var rec {{ ctx.s.name }} + if err := rows.Scan( + {%~ for f in ctx.select_fields %} + &rec.{{ f }}, + {%~ endfor %} + ); err != nil { + return nil, errors.WithStack(err) + } + records = append(records, rec) + } + + if err := rows.Close(); err != nil { + return nil, errors.WithStack(err) + } + + if err := rows.Err(); err != nil { + return nil, errors.WithStack(err) + } + + return records, nil +} +{% endmacro table_load %} + + +{% macro table_delete(ctx) %} +func Delete{{ ctx.s.name }}( + ctx context.Context, + dbe metadb.Execer, + {%~ for f in ctx.pkey %} + {{ f|varname }} {{ f.type|go_type }}, + {%~ endfor %} +) (bool, error) { + query := "DELETE FROM `{{ ctx.table_name }}` WHERE {{ ctx.delete_where_expr }}" + + res, err := dbe.ExecContext(ctx, query, + {%- for f in ctx.pkey -%} + {{ f|varname }}, + {%- endfor -%} + ) + if err != nil { + return false, errors.WithStack(err) + } + + rowsAffected, err := res.RowsAffected() + if err != nil { + return false, errors.WithStack(err) + } + + return rowsAffected > 0, nil +} + +{% if ctx.pkey|length > 1 %} +func Delete{{ ctx.s.name }}Collection( + ctx context.Context, + dbe metadb.Execer, + {{ ctx.delete_by|varname }} {{ ctx.delete_by.type|go_type }}, +) error { + query := "DELETE FROM `{{ ctx.table_name }}` WHERE `{{ ctx.delete_by.name }}` = ?" + + _, err := dbe.ExecContext(ctx, query, {{ ctx.delete_by|varname }}) + 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, + id {{ ctx.s.name }}Id, +) (bool, error) { + query := "DELETE FROM `{{ ctx.table_name }}` WHERE {{ ctx.delete_where_expr }}" + + res, err := dbe.ExecContext(ctx, query, + {%- for f in ctx.pkey -%} + id.{{ f|fname }}, + {%- endfor -%} + ) + if err != nil { + return false, errors.WithStack(err) + } + + rowsAffected, err := res.RowsAffected() + if err != nil { + return false, errors.WithStack(err) + } + + return rowsAffected > 0, nil +} + +{% set pkey_values_expr = arr_fill('?', ctx.pkey|length)|join(',') %} +func Delete{{ ctx.s.name }}CollectionById( + ctx context.Context, + dbe metadb.Execer, + ids []{{ ctx.s.name }}Id, +) error { + if len(ids) == 0 { + return nil + } + + tuples := make([]exp.LiteralExpression, 0, len(ids)) + for _, id := range ids { + tuples = append(tuples, goqu.L("({{ pkey_values_expr }})", + {% for f in ctx.pkey %} + id.{{ f|fname }}, + {% endfor %} + )) + } + + ds := metadb.SqlBuilder(). + Delete(goqu.T("level_state")). + Where(goqu.L("({{ pkey_values_expr }}) IN ?", + {% for f in ctx.pkey %} + goqu.C("{{ f.name }}"), + {% endfor %} + tuples, + )) + query, _, err := ds.ToSQL() + if err != nil { + return errors.WithStack(err) + } + + _, delErr := dbe.ExecContext(ctx, query) + return errors.WithStack(delErr) +} +{% endmacro table_delete_diff %} + + +{% macro changed_fields(type_name, fields) %} +{% for f in fields %} +func (s {{ type_name }}) {{ f|fname }}Changed() bool { + return s.changedFields.Changed("{{ f|alias }}") +} +{% endfor %} +{% endmacro changed_fields %} + + +{% macro json_sql_impl(type_name) %} +func (v *{{ type_name }}) Scan(value any) error { + jsonBytes, ok := value.([]byte) + if !ok { + return errors.New("incompatible type!") + } + + if err := json.Unmarshal(jsonBytes, v); err != nil { + return errors.Wrapf(err, "parsing json failed. source string: %s", jsonBytes) + } + + return nil +} +func (v {{ type_name }}) Value() (driver.Value, error) { + jsonBytes, err := json.Marshal(v) + if err != nil { + return nil, errors.WithStack(err) + } + + return jsonBytes, nil +} +{% endmacro json_sql_impl %} + + +{% macro json_set_expr(type_name, fields) %} +func (v {{ type_name }}) WriteJsonSetExpr(b *strings.Builder) error { + b.WriteString("JSON_SET(`kv`") + {%~ for f in fields -%} + {% set fname = f|fname %} + if v.{{ fname }}Changed() { + b.WriteString(",'$.{{ f|alias }}',") + {%~ if f.type is string %} + b.WriteString(strconv.Quote(v.{{ fname }})) + {%~ elseif f.type is bool %} + b.WriteString(strconv.FormatBool(v.{{ fname }})) + {%~ elseif f.type is uint %} + b.WriteString(strconv.FormatUint(uint64(v.{{ fname }}), 10)) + {%~ elseif f.type is int %} + b.WriteString(strconv.FormatInt(int64(v.{{ fname }}), 10)) + {%~ else %} + jsonBytes, err := json.Marshal(v.{{ fname }}) + if err != nil { + return errors.WithStack(err) + } + b.WriteString("CAST(") + b.WriteString(strconv.Quote(string(jsonBytes))) + b.WriteString(" AS JSON)") + {%~ endif %} + } + {%~ endfor %} + b.WriteRune(')') + + return nil +} +{% endmacro json_set_expr %} + + +{% macro meta_read(o, name) %} +{% set fields_len = o.fields|length %} +{% set opt_fields_len = count_optional(o.fields) %} +{% set all_fields = get_all_fields(o) %} + +func (s *{{ name|default(o.name) }}) Reset() { + {% if o.parent %} + s.{{o.parent.name}}.Reset() + {% endif %} + + {% for f in o.fields %} + {% set fname = f|fname %} + {% if f.type is array %} + if s.{{ fname }} == nil { + s.{{ fname }} = make({{ f.type|go_type }}, 0) + } else { + s.{{ fname }} = s.{{ fname }}[:0] + } + {% elseif f.type is struct %} + s.{{ fname }}.Reset() + {% else %} + s.{{ fname }} = {{ f|default_val }} + {% endif %} + {% endfor %} + + {% if has_token(o, 'bitfields') ~%} + s.fieldsMask = meta.FieldsMask{} + {%- endif ~%} + + {% if has_token(o, 'table') %} + s.changedFields.Reset() + {% endif %} +} + +func (s *{{ name|default(o.name) }}) Read(reader meta.Reader) error { + if err := reader.BeginContainer(""); err != nil { + return err + } + if err := s.ReadFields(reader); err != nil { + return err + } + return reader.EndContainer() +} + +func (s *{{ name|default(o.name) }}) ReadFields(reader meta.Reader) error { + s.Reset() + + readAsMap, err := reader.IsContainerAssoc() + if err != nil { + return err + } + if readAsMap { + return s.readFieldsAssociative(reader) + } + return s.readFields(reader) +} + +func (s *{{ name|default(o.name) }}) readFieldsAssociative(reader meta.Reader) error { + size, err := reader.ContainerSize() + if err != nil { + return err + } + + readFields := make(map[string]struct{}, {{ all_fields|length }}) + for ; size > 0; size-- { + var field string + if err := reader.ReadString(&field, ""); err != nil { + return err + } + + switch field { + {% for f in all_fields %} + case "{{ f|alias }}": + {{ _self.meta_read_field(f.type, 's.' ~ f|fname, f|alias, has_token(f, 'optional')) }} + {% endfor %} + default: + {# continue // do not return an error for nested structs #} + return errors.Errorf("unexpected field `%s`", field) + } + + readFields[field] = struct{}{} + {% if has_token(o, 'table') %} + s.changedFields.SetChanged(field) + {% endif %} + } + + for field := range _{{ name|default(o.name) }}RequiredFields { + if _, ok := readFields[field]; !ok { + return errors.Errorf("field `%s` is not present", field) + } + } + + return nil +} + +func (s *{{ name|default(o.name) }}) readFields(reader meta.Reader) error { + {% if has_token(o, 'bitfields') %} + use_mask, mask, err := reader.TryReadMask() + if err != nil { + return err + } + s.fieldsMask = mask + {% endif %} + + contSize, err := reader.ContainerSize() + if err != nil { + return err + } + + if contSize < {{ fields_len - opt_fields_len }} { + contSize = {{ fields_len - opt_fields_len }} + } + + {% if o.parent %} + if err := s.{{ o.parent.name }}.ReadFields(reader); err != nil { + return err + } + {% endif %} + + {% for f in o.fields %} + if contSize <= 0 { + return nil + } + + {% if has_token(o, 'bitfields') %} + if !use_mask { + contSize-- + } + if !use_mask || (use_mask && s.FieldChanged({{ loop.index0 }})) { + {{ _self.meta_read_field(f.type, 's.' ~ f|fname, f|alias, has_token(f, 'optional')) }} + } + {% else %} + contSize-- + {{ _self.meta_read_field(f.type, 's.' ~ f|fname, f|alias, has_token(f, 'optional')) }} + {% endif %} + + {%- endfor -%} + + return nil +} +{% endmacro meta_read %} + + +{% macro meta_read_field(type, fname, alias, optional) %} +{% if type is builtin %} + if err := reader.Read{{ type|builtin_type_suffix }}(&{{ fname }}, "{{ alias }}"); err != nil { + {% if optional %} + return nil + {% else %} + return err + {% endif %} + } +{% elseif type is enum %} + if err := reader.ReadInt32((*int32)(&{{ fname }}), "{{ alias }}"); err != nil { + {% if optional %} + return nil + {% else %} + return err + {% endif %} + } + if !{{ fname }}.IsValid() { + return errors.Errorf("bad enum value `%d` for `{{ alias }}`", {{ fname }}) + } +{% elseif type is struct %} + if err := {{ fname }}.Read(reader); err != nil { + {% if optional %} + return nil + {% else %} + return err + {% endif %} + } +{% elseif type is array %} + {% set name = fname|split('.')|last %} + {% set size_var = name|lcfirst ~ 'Size' %} + {% set tmp_var = 'tmp' ~ name %} + if err := reader.BeginContainer("{{ alias }}"); err != nil { + return err + } + {{ size_var }}, err := reader.ContainerSize() + if err != nil { + return err + } + for ; {{ size_var }} > 0; {{ size_var }}-- { + var {{ tmp_var }} {{ type.value|go_type }} + {{ _self.meta_read_field(type.value, tmp_var) }} + {{ fname }} = append({{ fname }}, {{ tmp_var }}) + } + if err := reader.EndContainer(); err != nil { + return err + } +{% else %} +{{ Error('unkown type ' ~ type) }} +{% endif %} +{% endmacro meta_read_field %} + + +{% macro meta_write(o, name) %} +func (s *{{ name|default(o.name) }}) Write(writer meta.Writer) error { + if err := writer.BeginAssocContainer({{ get_all_fields(o)|length }}, ""); err != nil { + return err + } + if err := s.WriteFields(writer); err != nil { + return err + } + return writer.EndContainer() +} + +func (s *{{ name|default(o.name) }}) WriteFields(writer meta.Writer) error { + {% if o.parent %} + if err := s.{{ o.parent.name }}.WriteFields(writer); err != nil { + return err + } + {% endif %} + + {% for f in o.fields %} + {{ _self.meta_write_field(f.type, 's.' ~ f|fname, f|alias) }} + {% endfor %} + + return nil +} +{% endmacro meta_write %} + + +{% macro meta_write_field(type, fname, alias) %} +{% if type is builtin %} + if err := writer.Write{{ type|builtin_type_suffix }}({{ fname }}, "{{ alias }}"); err != nil { + return err + } +{% elseif type is enum %} + if err := writer.WriteInt32(int32({{ fname }}), "{{ alias }}"); err != nil { + return err + } +{% elseif type is struct %} + if err := writer.BeginAssocContainer({{ get_all_fields(type)|length }}, "{{ alias }}"); err != nil { + return err + } + if err := {{ fname }}.WriteFields(writer); err != nil { + return err + } + if err := writer.EndContainer(); err != nil { + return err + } +{% elseif type is array %} + if err := writer.BeginContainer(len({{ fname }}), "{{ alias }}"); err != nil { + return err + } + for _, v := range {{ fname }} { + {{ _self.meta_write_field(type.value, 'v') }} + } + if err := writer.EndContainer(); err != nil { + return err + } +{% else %} +{{ Error('unknown type ' ~ type) }} +{% endif %} +{% 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 }} `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.struct_required_fields(s.name, s.fields) }} +{{ _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) %} +func New{{ name }}() *{{ name }} { + s := new({{ name }}) + s.Reset() + return s +} + +type I{{ name }} interface { + meta.Class + Ptr{{ name }}() *{{ name }} +} + +func {{ name }}ClassId() uint32 { + return {{ classid }} +} + +func ({{ name }}) ClassId() uint32 { + return {{ classid }} +} + +func ({{ name }}) ClassName() string { + return "{{ name }}" +} + +func (s *{{ name }}) Ptr{{ name }}() *{{ name }} { + return s +} +{% endmacro struct_methods %} + + +{% macro struct_required_fields(name, fields) %} +var _{{ name }}RequiredFields map[string]struct{} +func init() { + _{{ name }}RequiredFields = map[string]struct{}{ + {% for f in fields|filter(f => not has_token(f, 'optional')) %} + "{{ f|alias }}": {}, + {% endfor %} + } +} +{% endmacro struct_required_fields %} + +{% macro bitfields_methods(name) %} +func (s *{{ name }}) FieldChanged(index uint64) bool { + return s.fieldsMask.FieldChanged(index) +} + +func (s *{{ name }}) SetFieldChanged(index uint64) { + s.fieldsMask.SetFieldChanged(index) +} + +func (s *{{ name }}) HasChangedFields() bool { + return s.fieldsMask.IsFilled() +} + +func (s *{{ name }}) FieldsMask() meta.FieldsMask { + return s.fieldsMask +} +{% endmacro bitfields_methods %} + + +{% macro msgpack_decode(name, fields) %} +func (s *{{ name }}) DecodeMsgpack(dec *msgpack.Decoder) error { + l, err := dec.DecodeMapLen() + if err != nil { + return errors.WithStack(err) + } + + for i := 0; i < l; i++ { + field, err := dec.DecodeString() + if err != nil { + return errors.WithStack(err) + } + + switch field { + {%~ for f in fields %} + case "{{ f|alias }}": + {{ _self.msgpack_value_decode(f.type, 's.' ~ f|fname) }} + {%~ endfor %} + default: + return errors.Errorf("unexpected field `%s`", field) + } + } + + return nil +} +{% endmacro msgpack_decode %} + + +{% macro msgpack_value_decode(type, varname) %} +{%~ if type is builtin %} + v, err := dec.Decode{{ type|builtin_type_suffix }}() + if err != nil { + return errors.WithStack(err) + } + {{ varname }} = v +{%~ elseif type is enum %} + v, err := dec.DecodeInt32() + if err != nil { + return errors.WithStack(err) + } + {{ varname }} = {{ type.name }}(v) + if !{{ varname }}.IsValid() { + return errors.Errorf("bad enum value `%d` for `%s`", {{ varname }}, "{{ type.name }}") + } +{%~ elseif type is struct %} + if err := {{ varname }}.DecodeMsgpack(dec); err != nil { + return err + } +{%~ elseif type is array %} + size, err := dec.DecodeArrayLen() + if err != nil { + return errors.WithStack(err) + } + + for ; size > 0; size-- { + var tmp {{ type.value|go_type }} + {{ _self.msgpack_value_decode(type.value, 'tmp') }} + {{ varname }} = append({{ varname }}, tmp) + } +{%~ else %} + {{ Error('unexpected type ' ~ type) }} +{%~ endif %} +{% endmacro msgpack_value_decode %} + + +{% macro msgpack_encode(name, fields) %} +func (s *{{ name }}) EncodeMsgpack(enc *msgpack.Encoder) error { + if err := enc.EncodeMapLen({{ fields|length }}); err != nil { + return errors.WithStack(err) + } + + {% for f in fields -%} + + if err := enc.EncodeString("{{ f|alias }}"); err != nil { + return errors.WithStack(err) + } + {{ _self.msgpack_value_encode(f.type, 's.' ~ f|fname) }} + {% endfor %} + + return nil +} +{% endmacro msgpack_encode %} + + +{% macro msgpack_value_encode(type, varname) %} +{%~ if type is int %} + if err := enc.EncodeInt(int64({{ varname }})); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is uint %} + if err := enc.EncodeUint(uint64({{ varname }})); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is float %} + if err := enc.EncodeFloat32({{ varname }}); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is double %} + if err := enc.EncodeFloat64({{ varname }}); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is bool %} + if err := enc.EncodeBool({{ varname }}); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is string %} + if err := enc.EncodeString({{ varname }}); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is blob %} + if err := enc.EncodeBytes({{ varname }}); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is enum %} + if err := enc.EncodeInt(int64({{ varname }})); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is struct %} + if err := {{ varname }}.EncodeMsgpack(enc); err != nil { + return errors.WithStack(err) + } +{%~ elseif type is array %} + if err := enc.EncodeArrayLen(len({{ varname }})); err != nil { + return errors.WithStack(err) + } + for _, v := range {{ varname }} { + {{ _self.msgpack_value_encode(type.value, 'v') }} + } +{%~ else %} + {{ Error('unexpected type ' ~ type) }} +{%~ endif %} +{% endmacro msgpack_value_encode %} \ No newline at end of file