Skip to content

Commit 1eb51c7

Browse files
authored
Merge pull request #19063 from github/redsun82/codegen-rename-dbscheme
Codegen: add `ql.db_table_name` property pragma
2 parents e8e9403 + 4ff06e4 commit 1eb51c7

37 files changed

+346
-139
lines changed

.bazelrc

+1
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ build --java_language_version=17
3737
build --tool_java_language_version=17
3838
build --tool_java_runtime_version=remotejdk_17
3939
build --java_runtime_version=remotejdk_17
40+
build --@rules_python//python/config_settings:python_version=3.12
4041

4142
try-import %workspace%/local.bazelrc

.bazelrc.internal

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ common --registry=https://bcr.bazel.build
88
# its implementation packages without providing any code itself.
99
# We either can depend on internal implementation details, or turn of strict deps.
1010
common --@rules_dotnet//dotnet/settings:strict_deps=false
11+
12+
build --@rules_python//python/config_settings:python_version=3.12

MODULE.bazel

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ use_repo(csharp_main_extension, "paket.main")
155155
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
156156
pip.parse(
157157
hub_name = "codegen_deps",
158-
python_version = "3.11",
158+
python_version = "3.12",
159159
requirements_lock = "//misc/codegen:requirements_lock.txt",
160160
)
161161
use_repo(pip, "codegen_deps")

misc/codegen/.python-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.11
1+
3.12

misc/codegen/generators/cppgen.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,27 @@ def _get_type(t: str, add_or_none_except: typing.Optional[str] = None) -> str:
3737
return t
3838

3939

40+
def _get_trap_name(cls: schema.Class, p: schema.Property) -> str | None:
41+
if p.is_single:
42+
return None
43+
overridden_trap_name = p.pragmas.get("ql_db_table_name")
44+
if overridden_trap_name:
45+
return inflection.camelize(overridden_trap_name)
46+
trap_name = inflection.camelize(f"{cls.name}_{p.name}")
47+
if p.is_predicate:
48+
return trap_name
49+
return inflection.pluralize(trap_name)
50+
51+
4052
def _get_field(cls: schema.Class, p: schema.Property, add_or_none_except: typing.Optional[str] = None) -> cpp.Field:
41-
trap_name = None
42-
if not p.is_single:
43-
trap_name = inflection.camelize(f"{cls.name}_{p.name}")
44-
if not p.is_predicate:
45-
trap_name = inflection.pluralize(trap_name)
4653
args = dict(
4754
field_name=p.name + ("_" if p.name in cpp.cpp_keywords else ""),
4855
base_type=_get_type(p.type, add_or_none_except),
4956
is_optional=p.is_optional,
5057
is_repeated=p.is_repeated,
5158
is_predicate=p.is_predicate,
5259
is_unordered=p.is_unordered,
53-
trap_name=trap_name,
60+
trap_name=_get_trap_name(cls, p),
5461
)
5562
args.update(cpp.get_field_override(p.name))
5663
return cpp.Field(**args)

misc/codegen/generators/dbschemegen.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
log = logging.getLogger(__name__)
2525

2626

27+
class Error(Exception):
28+
pass
29+
30+
2731
def dbtype(typename: str, add_or_none_except: typing.Optional[str] = None) -> str:
2832
""" translate a type to a dbscheme counterpart, using `@lower_underscore` format for classes.
2933
For class types, appends an underscore followed by `null` if provided
@@ -65,11 +69,12 @@ def cls_to_dbscheme(cls: schema.Class, lookup: typing.Dict[str, schema.Class], a
6569
)
6670
# use property-specific tables for 1-to-many and 1-to-at-most-1 properties
6771
for f in cls.properties:
72+
overridden_table_name = f.pragmas.get("ql_db_table_name")
6873
if f.synth:
6974
continue
7075
if f.is_unordered:
7176
yield Table(
72-
name=inflection.tableize(f"{cls.name}_{f.name}"),
77+
name=overridden_table_name or inflection.tableize(f"{cls.name}_{f.name}"),
7378
columns=[
7479
Column("id", type=dbtype(cls.name)),
7580
Column(inflection.singularize(f.name), dbtype(f.type, add_or_none_except)),
@@ -79,7 +84,7 @@ def cls_to_dbscheme(cls: schema.Class, lookup: typing.Dict[str, schema.Class], a
7984
elif f.is_repeated:
8085
yield Table(
8186
keyset=KeySet(["id", "index"]),
82-
name=inflection.tableize(f"{cls.name}_{f.name}"),
87+
name=overridden_table_name or inflection.tableize(f"{cls.name}_{f.name}"),
8388
columns=[
8489
Column("id", type=dbtype(cls.name)),
8590
Column("index", type="int"),
@@ -90,7 +95,7 @@ def cls_to_dbscheme(cls: schema.Class, lookup: typing.Dict[str, schema.Class], a
9095
elif f.is_optional:
9196
yield Table(
9297
keyset=KeySet(["id"]),
93-
name=inflection.tableize(f"{cls.name}_{f.name}"),
98+
name=overridden_table_name or inflection.tableize(f"{cls.name}_{f.name}"),
9499
columns=[
95100
Column("id", type=dbtype(cls.name)),
96101
Column(f.name, dbtype(f.type, add_or_none_except)),
@@ -100,14 +105,25 @@ def cls_to_dbscheme(cls: schema.Class, lookup: typing.Dict[str, schema.Class], a
100105
elif f.is_predicate:
101106
yield Table(
102107
keyset=KeySet(["id"]),
103-
name=inflection.underscore(f"{cls.name}_{f.name}"),
108+
name=overridden_table_name or inflection.underscore(f"{cls.name}_{f.name}"),
104109
columns=[
105110
Column("id", type=dbtype(cls.name)),
106111
],
107112
dir=dir,
108113
)
109114

110115

116+
def check_name_conflicts(decls: list[Table | Union]):
117+
names = set()
118+
for decl in decls:
119+
match decl:
120+
case Table(name=name):
121+
if name in names:
122+
raise Error(f"Duplicate table name: {
123+
name}, you can use `@ql.db_table_name` on a property to resolve this")
124+
names.add(name)
125+
126+
111127
def get_declarations(data: schema.Schema):
112128
add_or_none_except = data.root_class.name if data.null else None
113129
declarations = [d for cls in data.classes.values() if not cls.imported for d in cls_to_dbscheme(cls,
@@ -120,6 +136,7 @@ def get_declarations(data: schema.Schema):
120136
declarations += [
121137
Union(dbtype(t, data.null), [dbtype(t), dbtype(data.null)]) for t in sorted(property_classes)
122138
]
139+
check_name_conflicts(declarations)
123140
return declarations
124141

125142

misc/codegen/generators/qlgen.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ def get_ql_property(cls: schema.Class, prop: schema.Property, lookup: typing.Dic
130130
internal="ql_internal" in prop.pragmas,
131131
)
132132
ql_name = prop.pragmas.get("ql_name", prop.name)
133+
db_table_name = prop.pragmas.get("ql_db_table_name")
134+
if db_table_name and prop.is_single:
135+
raise Error(f"`db_table_name` pragma is not supported for single properties, but {cls.name}.{prop.name} has it")
133136
if prop.is_single:
134137
args.update(
135138
singular=inflection.camelize(ql_name),
@@ -141,22 +144,22 @@ def get_ql_property(cls: schema.Class, prop: schema.Property, lookup: typing.Dic
141144
args.update(
142145
singular=inflection.singularize(inflection.camelize(ql_name)),
143146
plural=inflection.pluralize(inflection.camelize(ql_name)),
144-
tablename=inflection.tableize(f"{cls.name}_{prop.name}"),
147+
tablename=db_table_name or inflection.tableize(f"{cls.name}_{prop.name}"),
145148
tableparams=["this", "index", "result"] if not prop.is_unordered else ["this", "result"],
146149
doc=_get_doc(cls, prop, plural=False),
147150
doc_plural=_get_doc(cls, prop, plural=True),
148151
)
149152
elif prop.is_optional:
150153
args.update(
151154
singular=inflection.camelize(ql_name),
152-
tablename=inflection.tableize(f"{cls.name}_{prop.name}"),
155+
tablename=db_table_name or inflection.tableize(f"{cls.name}_{prop.name}"),
153156
tableparams=["this", "result"],
154157
doc=_get_doc(cls, prop),
155158
)
156159
elif prop.is_predicate:
157160
args.update(
158161
singular=inflection.camelize(ql_name, uppercase_first_letter=False),
159-
tablename=inflection.underscore(f"{cls.name}_{prop.name}"),
162+
tablename=db_table_name or inflection.underscore(f"{cls.name}_{prop.name}"),
160163
tableparams=["this"],
161164
doc=_get_doc(cls, prop),
162165
)

misc/codegen/generators/rustgen.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,28 @@ def _get_type(t: str) -> str:
2727
return t
2828

2929

30+
def _get_table_name(cls: schema.Class, p: schema.Property) -> str:
31+
if p.is_single:
32+
return inflection.tableize(cls.name)
33+
overridden_table_name = p.pragmas.get("ql_db_table_name")
34+
if overridden_table_name:
35+
return overridden_table_name
36+
table_name = f"{cls.name}_{p.name}"
37+
if p.is_predicate:
38+
return inflection.underscore(table_name)
39+
else:
40+
return inflection.tableize(table_name)
41+
42+
3043
def _get_field(cls: schema.Class, p: schema.Property) -> rust.Field:
31-
table_name = inflection.tableize(cls.name)
32-
if not p.is_single:
33-
table_name = f"{cls.name}_{p.name}"
34-
if p.is_predicate:
35-
table_name = inflection.underscore(table_name)
36-
else:
37-
table_name = inflection.tableize(table_name)
3844
args = dict(
3945
field_name=rust.avoid_keywords(p.name),
4046
base_type=_get_type(p.type),
4147
is_optional=p.is_optional,
4248
is_repeated=p.is_repeated,
4349
is_predicate=p.is_predicate,
4450
is_unordered=p.is_unordered,
45-
table_name=table_name,
51+
table_name=_get_table_name(cls, p),
4652
)
4753
args.update(rust.get_field_override(p.name))
4854
return rust.Field(**args)

misc/codegen/lib/schemadefs.py

+47-39
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
Callable as _Callable,
33
Dict as _Dict,
44
Iterable as _Iterable,
5-
ClassVar as _ClassVar,
5+
Union as _Union,
66
)
7+
from copy import deepcopy as _deepcopy
78
from misc.codegen.lib import schema as _schema
89
import inspect as _inspect
910
from dataclasses import dataclass as _dataclass
@@ -75,7 +76,7 @@ class _Namespace:
7576
""" simple namespacing mechanism """
7677
_name: str
7778

78-
def add(self, pragma: "_PragmaBase", key: str | None = None):
79+
def add(self, pragma: _Union["_PragmaBase", "_Parametrized"], key: str | None = None):
7980
self.__dict__[pragma.pragma] = pragma
8081
pragma.pragma = key or f"{self._name}_{pragma.pragma}"
8182

@@ -101,6 +102,10 @@ def negate(self) -> _schema.PropertyModifier:
101102
@_dataclass
102103
class _PragmaBase:
103104
pragma: str
105+
value: object = None
106+
107+
def _apply(self, pragmas: _Dict[str, object]) -> None:
108+
pragmas[self.pragma] = self.value
104109

105110

106111
@_dataclass
@@ -109,7 +114,6 @@ class _ClassPragma(_PragmaBase):
109114
For schema classes it acts as a python decorator with `@`.
110115
"""
111116
inherited: bool = False
112-
value: object = None
113117

114118
def __call__(self, cls: type) -> type:
115119
""" use this pragma as a decorator on classes """
@@ -122,23 +126,19 @@ def __call__(self, cls: type) -> type:
122126
self._apply(cls._pragmas)
123127
return cls
124128

125-
def _apply(self, pragmas: _Dict[str, object]) -> None:
126-
pragmas[self.pragma] = self.value
127-
128129

129130
@_dataclass
130-
class _Pragma(_ClassPragma, _schema.PropertyModifier):
131-
""" A class or property pragma.
132-
For properties, it functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
133-
For schema classes it acts as a python decorator with `@`.
131+
class _PropertyPragma(_PragmaBase, _schema.PropertyModifier):
132+
""" A property pragma.
133+
It functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
134134
"""
135135
remove: bool = False
136136

137137
def modify(self, prop: _schema.Property):
138138
self._apply(prop.pragmas)
139139

140140
def negate(self) -> _schema.PropertyModifier:
141-
return _Pragma(self.pragma, remove=True)
141+
return _PropertyPragma(self.pragma, remove=not self.remove)
142142

143143
def _apply(self, pragmas: _Dict[str, object]) -> None:
144144
if self.remove:
@@ -148,31 +148,38 @@ def _apply(self, pragmas: _Dict[str, object]) -> None:
148148

149149

150150
@_dataclass
151-
class _ParametrizedClassPragma(_PragmaBase):
152-
""" A class parametrized pragma.
153-
Needs to be applied to a parameter to give a class pragma.
151+
class _Pragma(_ClassPragma, _PropertyPragma):
152+
""" A class or property pragma.
153+
For properties, it functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
154+
For schema classes it acts as a python decorator with `@`.
154155
"""
155-
_pragma_class: _ClassVar[type] = _ClassPragma
156156

157-
inherited: bool = False
158-
factory: _Callable[..., object] = None
159157

160-
def __post_init__(self):
161-
self.__signature__ = _inspect.signature(self.factory).replace(return_annotation=self._pragma_class)
158+
class _Parametrized[P, **Q, T]:
159+
""" A parametrized pragma.
160+
Needs to be applied to a parameter to give a pragma.
161+
"""
162+
163+
def __init__(self, pragma_instance: P, factory: _Callable[Q, T]):
164+
self.pragma_instance = pragma_instance
165+
self.factory = factory
166+
self.__signature__ = _inspect.signature(self.factory).replace(return_annotation=type(self.pragma_instance))
162167

163-
def __call__(self, *args, **kwargs) -> _pragma_class:
164-
return self._pragma_class(self.pragma, self.inherited, value=self.factory(*args, **kwargs))
168+
@property
169+
def pragma(self):
170+
return self.pragma_instance.pragma
165171

172+
@pragma.setter
173+
def pragma(self, value):
174+
self.pragma_instance.pragma = value
166175

167-
@_dataclass
168-
class _ParametrizedPragma(_ParametrizedClassPragma):
169-
""" A class or property parametrized pragma.
170-
Needs to be applied to a parameter to give a pragma.
171-
"""
172-
_pragma_class: _ClassVar[type] = _Pragma
176+
def __invert__(self) -> "_Parametrized[P, Q, T]":
177+
return _Parametrized(~self.pragma_instance, factory=self.factory)
173178

174-
def __invert__(self) -> _Pragma:
175-
return _Pragma(self.pragma, remove=True)
179+
def __call__(self, *args: Q.args, **kwargs: Q.kwargs) -> T:
180+
ret = _deepcopy(self.pragma_instance)
181+
ret.value = self.factory(*args, **kwargs)
182+
return ret
176183

177184

178185
class _Optionalizer(_schema.PropertyModifier):
@@ -232,30 +239,31 @@ def __getitem__(self, item):
232239

233240
use_for_null = _ClassPragma("null")
234241

235-
qltest.add(_Pragma("skip"))
242+
qltest.add(_ClassPragma("skip"))
236243
qltest.add(_ClassPragma("collapse_hierarchy"))
237244
qltest.add(_ClassPragma("uncollapse_hierarchy"))
238-
qltest.add(_ParametrizedClassPragma("test_with", inherited=True, factory=_schema.get_type_name))
245+
qltest.add(_Parametrized(_ClassPragma("test_with", inherited=True), factory=_schema.get_type_name))
239246

240-
ql.add(_ParametrizedClassPragma("default_doc_name", factory=lambda doc: doc))
247+
ql.add(_Parametrized(_ClassPragma("default_doc_name"), factory=lambda doc: doc))
241248
ql.add(_ClassPragma("hideable", inherited=True))
242249
ql.add(_Pragma("internal"))
243-
ql.add(_ParametrizedPragma("name", factory=lambda name: name))
250+
ql.add(_Parametrized(_Pragma("name"), factory=lambda name: name))
251+
ql.add(_Parametrized(_PropertyPragma("db_table_name"), factory=lambda name: name))
244252

245253
cpp.add(_Pragma("skip"))
246254

247-
rust.add(_Pragma("detach"))
255+
rust.add(_PropertyPragma("detach"))
248256
rust.add(_Pragma("skip_doc_test"))
249257

250-
rust.add(_ParametrizedClassPragma("doc_test_signature", factory=lambda signature: signature))
258+
rust.add(_Parametrized(_ClassPragma("doc_test_signature"), factory=lambda signature: signature))
251259

252-
group = _ParametrizedClassPragma("group", inherited=True, factory=lambda group: group)
260+
group = _Parametrized(_ClassPragma("group", inherited=True), factory=lambda group: group)
253261

254262

255-
synth.add(_ParametrizedClassPragma("from_class", factory=lambda ref: _schema.SynthInfo(
263+
synth.add(_Parametrized(_ClassPragma("from_class"), factory=lambda ref: _schema.SynthInfo(
256264
from_class=_schema.get_type_name(ref))), key="synth")
257-
synth.add(_ParametrizedClassPragma("on_arguments", factory=lambda **kwargs:
258-
_schema.SynthInfo(on_arguments={k: _schema.get_type_name(t) for k, t in kwargs.items()})), key="synth")
265+
synth.add(_Parametrized(_ClassPragma("on_arguments"), factory=lambda **kwargs:
266+
_schema.SynthInfo(on_arguments={k: _schema.get_type_name(t) for k, t in kwargs.items()})), key="synth")
259267

260268

261269
@_dataclass(frozen=True)

0 commit comments

Comments
 (0)