lsst-felis 24.1.6rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of lsst-felis might be problematic. Click here for more details.
- felis/__init__.py +22 -0
- felis/cli.py +497 -0
- felis/datamodel.py +1116 -0
- felis/db/__init__.py +0 -0
- felis/db/dialects.py +116 -0
- felis/db/sqltypes.py +436 -0
- felis/db/utils.py +409 -0
- felis/db/variants.py +159 -0
- felis/metadata.py +383 -0
- felis/py.typed +0 -0
- felis/schemas/tap_schema_std.yaml +273 -0
- felis/tap.py +597 -0
- felis/tap_schema.py +644 -0
- felis/tests/__init__.py +0 -0
- felis/tests/postgresql.py +134 -0
- felis/tests/utils.py +122 -0
- felis/types.py +185 -0
- felis/version.py +2 -0
- lsst_felis-24.1.6rc1.dist-info/COPYRIGHT +1 -0
- lsst_felis-24.1.6rc1.dist-info/LICENSE +674 -0
- lsst_felis-24.1.6rc1.dist-info/METADATA +34 -0
- lsst_felis-24.1.6rc1.dist-info/RECORD +26 -0
- lsst_felis-24.1.6rc1.dist-info/WHEEL +5 -0
- lsst_felis-24.1.6rc1.dist-info/entry_points.txt +2 -0
- lsst_felis-24.1.6rc1.dist-info/top_level.txt +1 -0
- lsst_felis-24.1.6rc1.dist-info/zip-safe +1 -0
felis/metadata.py
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""Build SQLAlchemy metadata from a Felis schema."""
|
|
2
|
+
|
|
3
|
+
# This file is part of felis.
|
|
4
|
+
#
|
|
5
|
+
# Developed for the LSST Data Management System.
|
|
6
|
+
# This product includes software developed by the LSST Project
|
|
7
|
+
# (https://www.lsst.org).
|
|
8
|
+
# See the COPYRIGHT file at the top-level directory of this distribution
|
|
9
|
+
# for details of code ownership.
|
|
10
|
+
#
|
|
11
|
+
# This program is free software: you can redistribute it and/or modify
|
|
12
|
+
# it under the terms of the GNU General Public License as published by
|
|
13
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
14
|
+
# (at your option) any later version.
|
|
15
|
+
#
|
|
16
|
+
# This program is distributed in the hope that it will be useful,
|
|
17
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
# GNU General Public License for more details.
|
|
20
|
+
#
|
|
21
|
+
# You should have received a copy of the GNU General Public License
|
|
22
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from typing import Any, Literal
|
|
28
|
+
|
|
29
|
+
from lsst.utils.iteration import ensure_iterable
|
|
30
|
+
from sqlalchemy import (
|
|
31
|
+
CheckConstraint,
|
|
32
|
+
Column,
|
|
33
|
+
Constraint,
|
|
34
|
+
ForeignKeyConstraint,
|
|
35
|
+
Index,
|
|
36
|
+
MetaData,
|
|
37
|
+
PrimaryKeyConstraint,
|
|
38
|
+
Table,
|
|
39
|
+
TextClause,
|
|
40
|
+
UniqueConstraint,
|
|
41
|
+
text,
|
|
42
|
+
)
|
|
43
|
+
from sqlalchemy.dialects import mysql, postgresql
|
|
44
|
+
from sqlalchemy.types import TypeEngine
|
|
45
|
+
|
|
46
|
+
from felis.datamodel import Schema
|
|
47
|
+
from felis.db.variants import make_variant_dict
|
|
48
|
+
|
|
49
|
+
from . import datamodel
|
|
50
|
+
from .db import sqltypes
|
|
51
|
+
from .types import FelisType
|
|
52
|
+
|
|
53
|
+
__all__ = ("MetaDataBuilder", "get_datatype_with_variants")
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _handle_timestamp_column(column_obj: datamodel.Column, variant_dict: dict[str, TypeEngine[Any]]) -> None:
|
|
59
|
+
"""Handle columns with the timestamp datatype.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
column_obj
|
|
64
|
+
The column object representing the timestamp.
|
|
65
|
+
variant_dict
|
|
66
|
+
The dictionary of variant overrides for the datatype.
|
|
67
|
+
|
|
68
|
+
Notes
|
|
69
|
+
-----
|
|
70
|
+
This function updates the variant dictionary with the appropriate
|
|
71
|
+
timestamp type for the column object but only if the precision is set.
|
|
72
|
+
Otherwise, the default timestamp objects defined in the Felis type system
|
|
73
|
+
will be used instead.
|
|
74
|
+
"""
|
|
75
|
+
if column_obj.precision is not None:
|
|
76
|
+
args: Any = [False, column_obj.precision] # Turn off timezone.
|
|
77
|
+
variant_dict.update({"postgresql": postgresql.TIMESTAMP(*args), "mysql": mysql.DATETIME(*args)})
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_datatype_with_variants(column_obj: datamodel.Column) -> TypeEngine:
|
|
81
|
+
"""Use the Felis type system to get a SQLAlchemy datatype with variant
|
|
82
|
+
overrides from the information in a Felis column object.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
column_obj
|
|
87
|
+
The column object from which to get the datatype.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
`~sqlalchemy.types.TypeEngine`
|
|
92
|
+
The SQLAlchemy datatype object.
|
|
93
|
+
|
|
94
|
+
Raises
|
|
95
|
+
------
|
|
96
|
+
ValueError
|
|
97
|
+
Raised if the column has a sized type but no length or if the datatype
|
|
98
|
+
is invalid.
|
|
99
|
+
"""
|
|
100
|
+
variant_dict = make_variant_dict(column_obj)
|
|
101
|
+
felis_type = FelisType.felis_type(column_obj.datatype.value)
|
|
102
|
+
datatype_fun = getattr(sqltypes, column_obj.datatype.value, None)
|
|
103
|
+
if datatype_fun is None:
|
|
104
|
+
raise ValueError(f"Unknown datatype: {column_obj.datatype.value}")
|
|
105
|
+
args = []
|
|
106
|
+
if felis_type.is_sized:
|
|
107
|
+
# Add length argument for size types.
|
|
108
|
+
if not column_obj.length:
|
|
109
|
+
raise ValueError(f"Column {column_obj.name} has sized type '{column_obj.datatype}' but no length")
|
|
110
|
+
args = [column_obj.length]
|
|
111
|
+
if felis_type.is_timestamp:
|
|
112
|
+
_handle_timestamp_column(column_obj, variant_dict)
|
|
113
|
+
return datatype_fun(*args, **variant_dict)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_VALID_SERVER_DEFAULTS = ("CURRENT_TIMESTAMP", "NOW()", "LOCALTIMESTAMP", "NULL")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class MetaDataBuilder:
|
|
120
|
+
"""Build a SQLAlchemy metadata object from a Felis schema.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
schema
|
|
125
|
+
The schema object from which to build the SQLAlchemy metadata.
|
|
126
|
+
apply_schema_to_metadata
|
|
127
|
+
Whether to apply the schema name to the metadata object.
|
|
128
|
+
apply_schema_to_tables
|
|
129
|
+
Whether to apply the schema name to the tables.
|
|
130
|
+
ignore_constraints
|
|
131
|
+
Whether to ignore constraints when building the metadata.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
schema: Schema,
|
|
137
|
+
apply_schema_to_metadata: bool = True,
|
|
138
|
+
apply_schema_to_tables: bool = True,
|
|
139
|
+
ignore_constraints: bool = False,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Initialize the metadata builder."""
|
|
142
|
+
self.schema = schema
|
|
143
|
+
if not apply_schema_to_metadata:
|
|
144
|
+
logger.debug("Schema name will not be applied to metadata")
|
|
145
|
+
if not apply_schema_to_tables:
|
|
146
|
+
logger.debug("Schema name will not be applied to tables")
|
|
147
|
+
self.metadata = MetaData(schema=schema.name if apply_schema_to_metadata else None)
|
|
148
|
+
self._objects: dict[str, Any] = {}
|
|
149
|
+
self.apply_schema_to_tables = apply_schema_to_tables
|
|
150
|
+
self.ignore_constraints = ignore_constraints
|
|
151
|
+
|
|
152
|
+
def build(self) -> MetaData:
|
|
153
|
+
"""Build the SQLAlchemy tables and constraints from the schema.
|
|
154
|
+
|
|
155
|
+
Notes
|
|
156
|
+
-----
|
|
157
|
+
This first builds the tables and then makes a second pass to build the
|
|
158
|
+
constraints. This is necessary because the constraints may reference
|
|
159
|
+
objects that are not yet created when the tables are built.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
`~sqlalchemy.sql.schema.MetaData`
|
|
164
|
+
The SQLAlchemy metadata object.
|
|
165
|
+
"""
|
|
166
|
+
self.build_tables()
|
|
167
|
+
if not self.ignore_constraints:
|
|
168
|
+
self.build_constraints()
|
|
169
|
+
else:
|
|
170
|
+
logger.warning("Ignoring constraints")
|
|
171
|
+
return self.metadata
|
|
172
|
+
|
|
173
|
+
def build_tables(self) -> None:
|
|
174
|
+
"""Build the SQLAlchemy tables from the schema."""
|
|
175
|
+
for table in self.schema.tables:
|
|
176
|
+
self.build_table(table)
|
|
177
|
+
if table.primary_key:
|
|
178
|
+
primary_key = self.build_primary_key(table.primary_key)
|
|
179
|
+
self._objects[table.id].append_constraint(primary_key)
|
|
180
|
+
|
|
181
|
+
def build_primary_key(self, primary_key_columns: str | list[str]) -> PrimaryKeyConstraint:
|
|
182
|
+
"""Build a SQAlchemy ``PrimaryKeyConstraint`` from a single column ID
|
|
183
|
+
or a list of them.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
primary_key_columns
|
|
188
|
+
The column ID or list of column IDs from which to build the primary
|
|
189
|
+
key.
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
`~sqlalchemy.sql.schema.PrimaryKeyConstraint`
|
|
194
|
+
The SQLAlchemy primary key constraint object.
|
|
195
|
+
|
|
196
|
+
Notes
|
|
197
|
+
-----
|
|
198
|
+
The ``primary_key_columns`` is a string or a list of strings
|
|
199
|
+
representing IDs which will be used to find the columnn objects in the
|
|
200
|
+
builder's internal ID map.
|
|
201
|
+
"""
|
|
202
|
+
return PrimaryKeyConstraint(
|
|
203
|
+
*[self._objects[column_id] for column_id in ensure_iterable(primary_key_columns)]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def build_table(self, table_obj: datamodel.Table) -> None:
|
|
207
|
+
"""Build a SQLAlchemy ``Table`` from a Felis table and add it to the
|
|
208
|
+
metadata.
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
table_obj
|
|
213
|
+
The Felis table object from which to build the SQLAlchemy table.
|
|
214
|
+
|
|
215
|
+
Notes
|
|
216
|
+
-----
|
|
217
|
+
Several MySQL table options, including the engine and charset, are
|
|
218
|
+
handled by adding annotations to the table. This is not needed for
|
|
219
|
+
Postgres, as Felis does not support any table options for this dialect.
|
|
220
|
+
"""
|
|
221
|
+
# Process mysql table options.
|
|
222
|
+
optargs = {}
|
|
223
|
+
if table_obj.mysql_engine:
|
|
224
|
+
optargs["mysql_engine"] = table_obj.mysql_engine
|
|
225
|
+
if table_obj.mysql_charset:
|
|
226
|
+
optargs["mysql_charset"] = table_obj.mysql_charset
|
|
227
|
+
|
|
228
|
+
# Create the SQLAlchemy table object and its columns.
|
|
229
|
+
name = table_obj.name
|
|
230
|
+
id = table_obj.id
|
|
231
|
+
description = table_obj.description
|
|
232
|
+
columns = [self.build_column(column) for column in table_obj.columns]
|
|
233
|
+
table = Table(
|
|
234
|
+
name,
|
|
235
|
+
self.metadata,
|
|
236
|
+
*columns,
|
|
237
|
+
comment=description,
|
|
238
|
+
schema=self.schema.name if self.apply_schema_to_tables else None,
|
|
239
|
+
**optargs, # type: ignore[arg-type]
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Create the indexes and add them to the table.
|
|
243
|
+
indexes = [self.build_index(index) for index in table_obj.indexes]
|
|
244
|
+
for index in indexes:
|
|
245
|
+
index._set_parent(table)
|
|
246
|
+
table.indexes.add(index)
|
|
247
|
+
|
|
248
|
+
self._objects[id] = table
|
|
249
|
+
|
|
250
|
+
def build_column(self, column_obj: datamodel.Column) -> Column:
|
|
251
|
+
"""Build a SQLAlchemy ``Column`` from a Felis column object.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
column_obj
|
|
256
|
+
The column object from which to build the SQLAlchemy column.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
`~sqlalchemy.sql.schema.Column`
|
|
261
|
+
The SQLAlchemy column object.
|
|
262
|
+
"""
|
|
263
|
+
# Get basic column attributes.
|
|
264
|
+
name = column_obj.name
|
|
265
|
+
id = column_obj.id
|
|
266
|
+
description = column_obj.description
|
|
267
|
+
value = column_obj.value
|
|
268
|
+
nullable = column_obj.nullable
|
|
269
|
+
|
|
270
|
+
# Get datatype, handling variant overrides such as "mysql:datatype".
|
|
271
|
+
datatype = get_datatype_with_variants(column_obj)
|
|
272
|
+
|
|
273
|
+
# Set autoincrement, depending on if it was provided explicitly.
|
|
274
|
+
autoincrement: Literal["auto"] | bool = (
|
|
275
|
+
column_obj.autoincrement if column_obj.autoincrement is not None else "auto"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
server_default: str | TextClause | None = None
|
|
279
|
+
if value is not None:
|
|
280
|
+
server_default = str(value)
|
|
281
|
+
if server_default in _VALID_SERVER_DEFAULTS or not isinstance(value, str):
|
|
282
|
+
# If the server default is a valid keyword or not a string,
|
|
283
|
+
# use it as is.
|
|
284
|
+
server_default = text(server_default)
|
|
285
|
+
|
|
286
|
+
if server_default is not None:
|
|
287
|
+
logger.debug(f"Column '{id}' has default value: {server_default}")
|
|
288
|
+
|
|
289
|
+
column: Column = Column(
|
|
290
|
+
name,
|
|
291
|
+
datatype,
|
|
292
|
+
comment=description,
|
|
293
|
+
autoincrement=autoincrement,
|
|
294
|
+
nullable=nullable,
|
|
295
|
+
server_default=server_default,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
self._objects[id] = column
|
|
299
|
+
|
|
300
|
+
return column
|
|
301
|
+
|
|
302
|
+
def build_constraints(self) -> None:
|
|
303
|
+
"""Build the SQLAlchemy constraints from the Felis schema and append
|
|
304
|
+
them to the associated table in the metadata.
|
|
305
|
+
|
|
306
|
+
Notes
|
|
307
|
+
-----
|
|
308
|
+
This is performed as a separate step after building the tables so that
|
|
309
|
+
all the referenced objects in the constraints will be present and can
|
|
310
|
+
be looked up by their ID.
|
|
311
|
+
"""
|
|
312
|
+
for table_obj in self.schema.tables:
|
|
313
|
+
table = self._objects[table_obj.id]
|
|
314
|
+
for constraint_obj in table_obj.constraints:
|
|
315
|
+
constraint = self.build_constraint(constraint_obj)
|
|
316
|
+
table.append_constraint(constraint)
|
|
317
|
+
|
|
318
|
+
def build_constraint(self, constraint_obj: datamodel.Constraint) -> Constraint:
|
|
319
|
+
"""Build a SQLAlchemy ``Constraint`` from a Felis constraint.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
constraint_obj
|
|
324
|
+
The Felis object from which to build the constraint.
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
`~sqlalchemy.sql.schema.Constraint`
|
|
329
|
+
The SQLAlchemy constraint object.
|
|
330
|
+
|
|
331
|
+
Raises
|
|
332
|
+
------
|
|
333
|
+
ValueError
|
|
334
|
+
If the constraint type is not recognized.
|
|
335
|
+
TypeError
|
|
336
|
+
If the constraint object is not the expected type.
|
|
337
|
+
"""
|
|
338
|
+
args: dict[str, Any] = {
|
|
339
|
+
"name": constraint_obj.name or None,
|
|
340
|
+
"comment": constraint_obj.description or None,
|
|
341
|
+
"deferrable": constraint_obj.deferrable or None,
|
|
342
|
+
"initially": constraint_obj.initially or None,
|
|
343
|
+
}
|
|
344
|
+
constraint: Constraint
|
|
345
|
+
|
|
346
|
+
if isinstance(constraint_obj, datamodel.ForeignKeyConstraint):
|
|
347
|
+
fk_obj: datamodel.ForeignKeyConstraint = constraint_obj
|
|
348
|
+
columns = [self._objects[column_id] for column_id in fk_obj.columns]
|
|
349
|
+
refcolumns = [self._objects[column_id] for column_id in fk_obj.referenced_columns]
|
|
350
|
+
constraint = ForeignKeyConstraint(columns, refcolumns, **args)
|
|
351
|
+
elif isinstance(constraint_obj, datamodel.CheckConstraint):
|
|
352
|
+
check_obj: datamodel.CheckConstraint = constraint_obj
|
|
353
|
+
expression = check_obj.expression
|
|
354
|
+
constraint = CheckConstraint(expression, **args)
|
|
355
|
+
elif isinstance(constraint_obj, datamodel.UniqueConstraint):
|
|
356
|
+
uniq_obj: datamodel.UniqueConstraint = constraint_obj
|
|
357
|
+
columns = [self._objects[column_id] for column_id in uniq_obj.columns]
|
|
358
|
+
constraint = UniqueConstraint(*columns, **args)
|
|
359
|
+
else:
|
|
360
|
+
raise ValueError(f"Unknown constraint type: {type(constraint_obj)}")
|
|
361
|
+
|
|
362
|
+
self._objects[constraint_obj.id] = constraint
|
|
363
|
+
|
|
364
|
+
return constraint
|
|
365
|
+
|
|
366
|
+
def build_index(self, index_obj: datamodel.Index) -> Index:
|
|
367
|
+
"""Build a SQLAlchemy ``Index`` from a Felis `~felis.datamodel.Index`.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
index_obj
|
|
372
|
+
The Felis object from which to build the SQLAlchemy index.
|
|
373
|
+
|
|
374
|
+
Returns
|
|
375
|
+
-------
|
|
376
|
+
`~sqlalchemy.sql.schema.Index`
|
|
377
|
+
The SQLAlchemy index object.
|
|
378
|
+
"""
|
|
379
|
+
columns = [self._objects[c_id] for c_id in (index_obj.columns if index_obj.columns else [])]
|
|
380
|
+
expressions = index_obj.expressions if index_obj.expressions else []
|
|
381
|
+
index = Index(index_obj.name, *columns, *expressions)
|
|
382
|
+
self._objects[index_obj.id] = index
|
|
383
|
+
return index
|
felis/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
name: TAP_SCHEMA
|
|
2
|
+
version: "1.1"
|
|
3
|
+
description: A TAP-standard-mandated schema to describe tablesets in a TAP 1.1 service
|
|
4
|
+
tables:
|
|
5
|
+
- name: "schemas"
|
|
6
|
+
description: description of schemas in this tableset
|
|
7
|
+
primaryKey: "#schemas.schema_name"
|
|
8
|
+
tap:table_index: 100000
|
|
9
|
+
mysql:engine: "InnoDB"
|
|
10
|
+
columns:
|
|
11
|
+
- name: "schema_name"
|
|
12
|
+
datatype: "string"
|
|
13
|
+
description: schema name for reference to tap_schema.schemas
|
|
14
|
+
length: 64
|
|
15
|
+
nullable: false
|
|
16
|
+
tap:principal: 1
|
|
17
|
+
tap:std: 1
|
|
18
|
+
tap:column_index: 1
|
|
19
|
+
- name: "utype"
|
|
20
|
+
datatype: "string"
|
|
21
|
+
description: lists the utypes of schemas in the tableset
|
|
22
|
+
length: 512
|
|
23
|
+
tap:principal: 1
|
|
24
|
+
tap:std: 1
|
|
25
|
+
tap:column_index: 2
|
|
26
|
+
- name: "description"
|
|
27
|
+
datatype: "string"
|
|
28
|
+
description: describes schemas in the tableset
|
|
29
|
+
length: 512
|
|
30
|
+
tap:principal: 1
|
|
31
|
+
tap:std: 1
|
|
32
|
+
tap:column_index: 3
|
|
33
|
+
- name: "schema_index"
|
|
34
|
+
datatype: "int"
|
|
35
|
+
description: recommended sort order when listing schemas
|
|
36
|
+
tap:principal: 1
|
|
37
|
+
tap:std: 1
|
|
38
|
+
tap:column_index: 4
|
|
39
|
+
- name: "tables"
|
|
40
|
+
description: description of tables in this tableset
|
|
41
|
+
primaryKey: "#tables.table_name"
|
|
42
|
+
tap:table_index: 101000
|
|
43
|
+
mysql:engine: "InnoDB"
|
|
44
|
+
columns:
|
|
45
|
+
- name: schema_name
|
|
46
|
+
datatype: string
|
|
47
|
+
description: the schema this table belongs to
|
|
48
|
+
length: 64
|
|
49
|
+
nullable: false
|
|
50
|
+
tap:principal: 1
|
|
51
|
+
tap:std: 1
|
|
52
|
+
tap:column_index: 1
|
|
53
|
+
- name: table_name
|
|
54
|
+
datatype: string
|
|
55
|
+
description: the fully qualified table name
|
|
56
|
+
length: 128
|
|
57
|
+
nullable: false
|
|
58
|
+
tap:principal: 1
|
|
59
|
+
tap:std: 1
|
|
60
|
+
tap:column_index: 2
|
|
61
|
+
- name: table_type
|
|
62
|
+
datatype: string
|
|
63
|
+
description: "one of: table view"
|
|
64
|
+
length: 8
|
|
65
|
+
nullable: false
|
|
66
|
+
tap:principal: 1
|
|
67
|
+
tap:std: 1
|
|
68
|
+
tap:column_index: 3
|
|
69
|
+
- name: utype
|
|
70
|
+
datatype: string
|
|
71
|
+
description: lists the utype of tables in the tableset
|
|
72
|
+
length: 512
|
|
73
|
+
tap:principal: 1
|
|
74
|
+
tap:std: 1
|
|
75
|
+
tap:column_index: 4
|
|
76
|
+
- name: description
|
|
77
|
+
datatype: string
|
|
78
|
+
description: describes tables in the tableset
|
|
79
|
+
length: 512
|
|
80
|
+
tap:principal: 1
|
|
81
|
+
tap:std: 1
|
|
82
|
+
tap:column_index: 5
|
|
83
|
+
- name: table_index
|
|
84
|
+
datatype: int
|
|
85
|
+
description: recommended sort order when listing tables
|
|
86
|
+
tap:principal: 1
|
|
87
|
+
tap:std: 1
|
|
88
|
+
tap:column_index: 6
|
|
89
|
+
constraints:
|
|
90
|
+
- name: "k1"
|
|
91
|
+
"@type": ForeignKey
|
|
92
|
+
columns: ["#tables.schema_name"]
|
|
93
|
+
referencedColumns: ["#schemas.schema_name"]
|
|
94
|
+
- name: "columns"
|
|
95
|
+
description: description of columns in this tableset
|
|
96
|
+
primaryKey: ["#columns.table_name", "#columns.column_name"]
|
|
97
|
+
tap_table_index: 102000
|
|
98
|
+
mysql:engine: "InnoDB"
|
|
99
|
+
columns:
|
|
100
|
+
- name: table_name
|
|
101
|
+
datatype: string
|
|
102
|
+
description: the table this column belongs to
|
|
103
|
+
length: 128
|
|
104
|
+
nullable: false
|
|
105
|
+
tap:principal: 1
|
|
106
|
+
tap:std: 1
|
|
107
|
+
tap:column_index: 1
|
|
108
|
+
- name: column_name
|
|
109
|
+
datatype: string
|
|
110
|
+
description: the column name
|
|
111
|
+
length: 64
|
|
112
|
+
nullable: false
|
|
113
|
+
tap:principal: 1
|
|
114
|
+
tap:std: 1
|
|
115
|
+
tap:column_index: 2
|
|
116
|
+
- name: utype
|
|
117
|
+
datatype: string
|
|
118
|
+
description: lists the utypes of columns in the tableset
|
|
119
|
+
length: 512
|
|
120
|
+
tap:principal: 1
|
|
121
|
+
tap:std: 1
|
|
122
|
+
tap:column_index: 3
|
|
123
|
+
- name: ucd
|
|
124
|
+
datatype: string
|
|
125
|
+
description: lists the UCDs of columns in the tableset
|
|
126
|
+
length: 64
|
|
127
|
+
tap:principal: 1
|
|
128
|
+
tap:std: 1
|
|
129
|
+
tap:column_index: 4
|
|
130
|
+
- name: unit
|
|
131
|
+
datatype: string
|
|
132
|
+
description: lists the unit used for column values in the tableset
|
|
133
|
+
length: 64
|
|
134
|
+
tap:principal: 1
|
|
135
|
+
tap:std: 1
|
|
136
|
+
tap:column_index: 5
|
|
137
|
+
- name: description
|
|
138
|
+
datatype: string
|
|
139
|
+
description: describes the columns in the tableset
|
|
140
|
+
length: 512
|
|
141
|
+
tap:principal: 1
|
|
142
|
+
tap:std: 1
|
|
143
|
+
tap:column_index: 6
|
|
144
|
+
- name: datatype
|
|
145
|
+
datatype: string
|
|
146
|
+
description: lists the ADQL datatype of columns in the tableset
|
|
147
|
+
length: 64
|
|
148
|
+
nullable: false
|
|
149
|
+
tap:principal: 1
|
|
150
|
+
tap:std: 1
|
|
151
|
+
tap:column_index: 7
|
|
152
|
+
- name: arraysize
|
|
153
|
+
datatype: string
|
|
154
|
+
description: lists the size of variable-length columns in the tableset
|
|
155
|
+
length: 16
|
|
156
|
+
tap:principal: 1
|
|
157
|
+
tap:std: 1
|
|
158
|
+
tap:column_index: 8
|
|
159
|
+
- name: xtype
|
|
160
|
+
datatype: string
|
|
161
|
+
description: a DALI or custom extended type annotation
|
|
162
|
+
length: 64
|
|
163
|
+
tap:principal: 1
|
|
164
|
+
tap:std: 1
|
|
165
|
+
tap:column_index: 9
|
|
166
|
+
- name: size
|
|
167
|
+
datatype: int
|
|
168
|
+
description: "deprecated: use arraysize"
|
|
169
|
+
tap:principal: 1
|
|
170
|
+
tap:std: 1
|
|
171
|
+
tap:column_index: 10
|
|
172
|
+
- name: principal
|
|
173
|
+
datatype: int
|
|
174
|
+
description: a principal column; 1 means 1, 0 means 0
|
|
175
|
+
nullable: false
|
|
176
|
+
tap:principal: 1
|
|
177
|
+
tap:std: 1
|
|
178
|
+
tap:column_index: 11
|
|
179
|
+
- name: indexed
|
|
180
|
+
datatype: int
|
|
181
|
+
description: an indexed column; 1 means 1, 0 means 0
|
|
182
|
+
nullable: false
|
|
183
|
+
tap:principal: 1
|
|
184
|
+
tap:std: 1
|
|
185
|
+
tap:column_index: 12
|
|
186
|
+
- name: std
|
|
187
|
+
datatype: int
|
|
188
|
+
description: a standard column; 1 means 1, 0 means 0
|
|
189
|
+
nullable: false
|
|
190
|
+
tap:principal: 1
|
|
191
|
+
tap:std: 1
|
|
192
|
+
tap:column_index: 13
|
|
193
|
+
- name: column_index
|
|
194
|
+
datatype: int
|
|
195
|
+
description: recommended sort order when listing columns
|
|
196
|
+
tap:principal: 1
|
|
197
|
+
tap:std: 1
|
|
198
|
+
tap:column_index: 14
|
|
199
|
+
constraints:
|
|
200
|
+
- name: "k2"
|
|
201
|
+
"@type": ForeignKey
|
|
202
|
+
columns: ["#columns.table_name"]
|
|
203
|
+
referencedColumns: ["#tables.table_name"]
|
|
204
|
+
- name: "keys"
|
|
205
|
+
description: description of foreign keys in this tableset
|
|
206
|
+
primaryKey: "#keys.key_id"
|
|
207
|
+
tap:table_index: 103000
|
|
208
|
+
mysql:engine: "InnoDB"
|
|
209
|
+
columns:
|
|
210
|
+
- name: key_id
|
|
211
|
+
datatype: string
|
|
212
|
+
description: unique key to join to tap_schema.key_columns
|
|
213
|
+
length: 64
|
|
214
|
+
nullable: false
|
|
215
|
+
- name: from_table
|
|
216
|
+
datatype: string
|
|
217
|
+
description: the table with the foreign key
|
|
218
|
+
length: 128
|
|
219
|
+
nullable: false
|
|
220
|
+
- name: target_table
|
|
221
|
+
datatype: string
|
|
222
|
+
description: the table with the primary key
|
|
223
|
+
length: 128
|
|
224
|
+
nullable: false
|
|
225
|
+
- name: utype
|
|
226
|
+
datatype: string
|
|
227
|
+
description: lists the utype of keys in the tableset
|
|
228
|
+
length: 512
|
|
229
|
+
- name: description
|
|
230
|
+
datatype: string
|
|
231
|
+
description: describes keys in the tableset
|
|
232
|
+
length: 512
|
|
233
|
+
constraints:
|
|
234
|
+
- name: "k3"
|
|
235
|
+
"@type": ForeignKey
|
|
236
|
+
columns: ["#keys.from_table"]
|
|
237
|
+
referencedColumns: ["#tables.table_name"]
|
|
238
|
+
- name: "k4"
|
|
239
|
+
"@type": ForeignKey
|
|
240
|
+
columns: ["#keys.target_table"]
|
|
241
|
+
referencedColumns: ["#tables.table_name"]
|
|
242
|
+
- name: "key_columns"
|
|
243
|
+
description: description of foreign key columns in this tableset
|
|
244
|
+
tap:table_index: 104000
|
|
245
|
+
mysql:engine: "InnoDB"
|
|
246
|
+
columns:
|
|
247
|
+
- name: key_id
|
|
248
|
+
datatype: string
|
|
249
|
+
length: 64
|
|
250
|
+
nullable: false
|
|
251
|
+
- name: from_column
|
|
252
|
+
datatype: string
|
|
253
|
+
length: 64
|
|
254
|
+
nullable: false
|
|
255
|
+
- name: target_column
|
|
256
|
+
datatype: string
|
|
257
|
+
length: 64
|
|
258
|
+
nullable: false
|
|
259
|
+
constraints:
|
|
260
|
+
- name: "k5"
|
|
261
|
+
"@type": ForeignKey
|
|
262
|
+
columns: ["#key_columns.key_id"]
|
|
263
|
+
referencedColumns: ["#keys.key_id"]
|
|
264
|
+
# FIXME: These can't be defined as FK constraints, because they refer
|
|
265
|
+
# to non-unique columns, e.g., column_name from the columns table.
|
|
266
|
+
# - name: "k6"
|
|
267
|
+
# "@type": ForeignKey
|
|
268
|
+
# columns: ["#key_columns.from_column"]
|
|
269
|
+
# referencedColumns: ["#columns.column_name"]
|
|
270
|
+
# - name: "k7"
|
|
271
|
+
# "@type": ForeignKey
|
|
272
|
+
# columns: ["#key_columns.target_column"]
|
|
273
|
+
# referencedColumns: ["#columns.column_name"]
|