lsst-felis 28.2024.4500__py3-none-any.whl → 29.2025.4500__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.
felis/__init__.py CHANGED
@@ -19,4 +19,15 @@
19
19
  # You should have received a copy of the GNU General Public License
20
20
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
21
21
 
22
- from .version import *
22
+ from .datamodel import Schema
23
+ from .db.schema import create_database
24
+ from .diff import DatabaseDiff, FormattedSchemaDiff, SchemaDiff
25
+ from .metadata import MetaDataBuilder
26
+
27
+ from importlib.metadata import PackageNotFoundError, version
28
+
29
+ try:
30
+ __version__ = version("lsst-felis")
31
+ except PackageNotFoundError:
32
+ # Package not installed or scons not run.
33
+ __version__ = "0.0.0"
felis/cli.py CHANGED
@@ -34,10 +34,11 @@ from sqlalchemy.engine.mock import MockConnection, create_mock_engine
34
34
 
35
35
  from . import __version__
36
36
  from .datamodel import Schema
37
+ from .db.schema import create_database
37
38
  from .db.utils import DatabaseContext, is_mock_url
39
+ from .diff import DatabaseDiff, FormattedSchemaDiff, SchemaDiff
38
40
  from .metadata import MetaDataBuilder
39
- from .tap import Tap11Base, TapLoadingVisitor, init_tables
40
- from .tap_schema import DataLoader, TableManager
41
+ from .tap_schema import DataLoader, MetadataInserter, TableManager
41
42
 
42
43
  __all__ = ["cli"]
43
44
 
@@ -62,7 +63,10 @@ loglevel_choices = ["CRITICAL", "FATAL", "ERROR", "WARNING", "INFO", "DEBUG"]
62
63
  help="Felis log file path",
63
64
  )
64
65
  @click.option(
65
- "--id-generation", is_flag=True, help="Generate IDs for all objects that do not have them", default=False
66
+ "--id-generation/--no-id-generation",
67
+ is_flag=True,
68
+ help="Generate IDs for all objects that do not have them",
69
+ default=True,
66
70
  )
67
71
  @click.pass_context
68
72
  def cli(ctx: click.Context, log_level: str, log_file: str | None, id_generation: bool) -> None:
@@ -71,6 +75,8 @@ def cli(ctx: click.Context, log_level: str, log_file: str | None, id_generation:
71
75
  ctx.obj["id_generation"] = id_generation
72
76
  if ctx.obj["id_generation"]:
73
77
  logger.info("ID generation is enabled")
78
+ else:
79
+ logger.info("ID generation is disabled")
74
80
  if log_file:
75
81
  logging.basicConfig(filename=log_file, level=log_level)
76
82
  else:
@@ -177,181 +183,34 @@ def create(
177
183
  raise click.ClickException(str(e))
178
184
 
179
185
 
180
- @cli.command("init-tap", help="Initialize TAP_SCHEMA objects in the database")
181
- @click.option("--tap-schema-name", help="Alternate database schema name for 'TAP_SCHEMA'")
182
- @click.option("--tap-schemas-table", help="Alternate table name for 'schemas'")
183
- @click.option("--tap-tables-table", help="Alternate table name for 'tables'")
184
- @click.option("--tap-columns-table", help="Alternate table name for 'columns'")
185
- @click.option("--tap-keys-table", help="Alternate table name for 'keys'")
186
- @click.option("--tap-key-columns-table", help="Alternate table name for 'key_columns'")
187
- @click.argument("engine-url")
188
- def init_tap(
189
- engine_url: str,
190
- tap_schema_name: str,
191
- tap_schemas_table: str,
192
- tap_tables_table: str,
193
- tap_columns_table: str,
194
- tap_keys_table: str,
195
- tap_key_columns_table: str,
196
- ) -> None:
197
- """Initialize TAP_SCHEMA objects in the database.
198
-
199
- Parameters
200
- ----------
201
- engine_url
202
- SQLAlchemy Engine URL. The target PostgreSQL schema or MySQL database
203
- must already exist and be referenced in the URL.
204
- tap_schema_name
205
- Alterate name for the database schema ``TAP_SCHEMA``.
206
- tap_schemas_table
207
- Alterate table name for ``schemas``.
208
- tap_tables_table
209
- Alterate table name for ``tables``.
210
- tap_columns_table
211
- Alterate table name for ``columns``.
212
- tap_keys_table
213
- Alterate table name for ``keys``.
214
- tap_key_columns_table
215
- Alterate table name for ``key_columns``.
216
-
217
- Notes
218
- -----
219
- The supported version of TAP_SCHEMA in the SQLAlchemy metadata is 1.1. The
220
- tables are created in the database schema specified by the engine URL,
221
- which must be a PostgreSQL schema or MySQL database that already exists.
222
- """
223
- engine = create_engine(engine_url)
224
- init_tables(
225
- tap_schema_name,
226
- tap_schemas_table,
227
- tap_tables_table,
228
- tap_columns_table,
229
- tap_keys_table,
230
- tap_key_columns_table,
231
- )
232
- Tap11Base.metadata.create_all(engine)
233
-
234
-
235
- @cli.command("load-tap", help="Load metadata from a Felis file into a TAP_SCHEMA database")
236
- @click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
237
- @click.option("--schema-name", help="Alternate Schema Name for Felis file")
238
- @click.option("--catalog-name", help="Catalog Name for Schema")
239
- @click.option("--dry-run", is_flag=True, help="Dry Run Only. Prints out the DDL that would be executed")
240
- @click.option("--tap-schema-name", help="Alternate schema name for 'TAP_SCHEMA'")
241
- @click.option("--tap-tables-postfix", help="Postfix for TAP_SCHEMA table names")
242
- @click.option("--tap-schemas-table", help="Alternate table name for 'schemas'")
243
- @click.option("--tap-tables-table", help="Alternate table name for 'tables'")
244
- @click.option("--tap-columns-table", help="Alternate table name for 'columns'")
245
- @click.option("--tap-keys-table", help="Alternate table name for 'keys'")
246
- @click.option("--tap-key-columns-table", help="Alternate table name for 'key_columns'")
247
- @click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema in this environment")
248
- @click.argument("file", type=click.File())
249
- def load_tap(
250
- engine_url: str,
251
- schema_name: str,
252
- catalog_name: str,
253
- dry_run: bool,
254
- tap_schema_name: str,
255
- tap_tables_postfix: str,
256
- tap_schemas_table: str,
257
- tap_tables_table: str,
258
- tap_columns_table: str,
259
- tap_keys_table: str,
260
- tap_key_columns_table: str,
261
- tap_schema_index: int,
262
- file: IO[str],
263
- ) -> None:
264
- """Load TAP metadata from a Felis file.
265
-
266
- This command loads the associated TAP metadata from a Felis YAML file
267
- into the TAP_SCHEMA tables.
268
-
269
- Parameters
270
- ----------
271
- engine_url
272
- SQLAlchemy Engine URL to catalog.
273
- schema_name
274
- Alternate schema name. This overrides the schema name in the
275
- ``catalog`` field of the Felis file.
276
- catalog_name
277
- Catalog name for the schema. This possibly duplicates the
278
- ``tap_schema_name`` argument (DM-44870).
279
- dry_run
280
- Dry run only to print out commands instead of executing.
281
- tap_schema_name
282
- Alternate name for the schema of TAP_SCHEMA in the database.
283
- tap_tables_postfix
284
- Postfix for TAP table names that will be automatically appended.
285
- tap_schemas_table
286
- Alternate table name for ``schemas``.
287
- tap_tables_table
288
- Alternate table name for ``tables``.
289
- tap_columns_table
290
- Alternate table name for ``columns``.
291
- tap_keys_table
292
- Alternate table name for ``keys``.
293
- tap_key_columns_table
294
- Alternate table name for ``key_columns``.
295
- tap_schema_index
296
- TAP_SCHEMA index of the schema in this TAP environment.
297
- file
298
- Felis file to read.
299
-
300
- Notes
301
- -----
302
- The data will be loaded into the TAP_SCHEMA from the engine URL. The
303
- tables must have already been initialized or an error will occur.
304
- """
305
- schema = Schema.from_stream(file)
306
-
307
- tap_tables = init_tables(
308
- tap_schema_name,
309
- tap_tables_postfix,
310
- tap_schemas_table,
311
- tap_tables_table,
312
- tap_columns_table,
313
- tap_keys_table,
314
- tap_key_columns_table,
315
- )
316
-
317
- if not dry_run:
318
- engine = create_engine(engine_url)
319
-
320
- if engine_url == "sqlite://" and not dry_run:
321
- # In Memory SQLite - Mostly used to test
322
- Tap11Base.metadata.create_all(engine)
323
-
324
- tap_visitor = TapLoadingVisitor(
325
- engine,
326
- catalog_name=catalog_name,
327
- schema_name=schema_name,
328
- tap_tables=tap_tables,
329
- tap_schema_index=tap_schema_index,
330
- )
331
- tap_visitor.visit_schema(schema)
332
- else:
333
- conn = DatabaseContext.create_mock_engine(engine_url)
334
-
335
- tap_visitor = TapLoadingVisitor.from_mock_connection(
336
- conn,
337
- catalog_name=catalog_name,
338
- schema_name=schema_name,
339
- tap_tables=tap_tables,
340
- tap_schema_index=tap_schema_index,
341
- )
342
- tap_visitor.visit_schema(schema)
343
-
344
-
345
186
  @cli.command("load-tap-schema", help="Load metadata from a Felis file into a TAP_SCHEMA database")
346
187
  @click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
347
- @click.option("--tap-schema-name", help="Name of the TAP_SCHEMA schema in the database")
348
188
  @click.option(
349
- "--tap-tables-postfix", help="Postfix which is applied to standard TAP_SCHEMA table names", default=""
189
+ "--tap-schema-name", "-n", help="Name of the TAP_SCHEMA schema in the database (default: TAP_SCHEMA)"
190
+ )
191
+ @click.option(
192
+ "--tap-tables-postfix",
193
+ "-p",
194
+ help="Postfix which is applied to standard TAP_SCHEMA table names",
195
+ default="",
196
+ )
197
+ @click.option("--tap-schema-index", "-i", type=int, help="TAP_SCHEMA index of the schema in this environment")
198
+ @click.option("--dry-run", "-D", is_flag=True, help="Execute dry run only. Does not insert any data.")
199
+ @click.option("--echo", "-e", is_flag=True, help="Print out the generated insert statements to stdout")
200
+ @click.option("--output-file", "-o", type=click.Path(), help="Write SQL commands to a file")
201
+ @click.option(
202
+ "--force-unbounded-arraysize",
203
+ is_flag=True,
204
+ help="Use unbounded arraysize by default for all variable length string columns"
205
+ ", e.g., ``votable:arraysize: *`` (workaround for astropy bug #18099)",
206
+ ) # DM-50899: Variable-length bounded strings are not handled correctly in astropy
207
+ @click.option(
208
+ "--unique-keys",
209
+ "-u",
210
+ is_flag=True,
211
+ help="Generate unique key_id values for keys and key_columns tables by prepending the schema name",
212
+ default=False,
350
213
  )
351
- @click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema in this environment")
352
- @click.option("--dry-run", is_flag=True, help="Execute dry run only. Does not insert any data.")
353
- @click.option("--echo", is_flag=True, help="Print out the generated insert statements to stdout")
354
- @click.option("--output-file", type=click.Path(), help="Write SQL commands to a file")
355
214
  @click.argument("file", type=click.File())
356
215
  @click.pass_context
357
216
  def load_tap_schema(
@@ -363,6 +222,8 @@ def load_tap_schema(
363
222
  dry_run: bool,
364
223
  echo: bool,
365
224
  output_file: str | None,
225
+ force_unbounded_arraysize: bool,
226
+ unique_keys: bool,
366
227
  file: IO[str],
367
228
  ) -> None:
368
229
  """Load TAP metadata from a Felis file.
@@ -402,7 +263,13 @@ def load_tap_schema(
402
263
  table_name_postfix=tap_tables_postfix,
403
264
  )
404
265
 
405
- schema = Schema.from_stream(file, context={"id_generation": ctx.obj["id_generation"]})
266
+ schema = Schema.from_stream(
267
+ file,
268
+ context={
269
+ "id_generation": ctx.obj["id_generation"],
270
+ "force_unbounded_arraysize": force_unbounded_arraysize,
271
+ },
272
+ )
406
273
 
407
274
  DataLoader(
408
275
  schema,
@@ -412,9 +279,57 @@ def load_tap_schema(
412
279
  dry_run=dry_run,
413
280
  print_sql=echo,
414
281
  output_path=output_file,
282
+ unique_keys=unique_keys,
415
283
  ).load()
416
284
 
417
285
 
286
+ @cli.command("init-tap-schema", help="Initialize a standard TAP_SCHEMA database")
287
+ @click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL", required=True)
288
+ @click.option("--tap-schema-name", help="Name of the TAP_SCHEMA schema in the database")
289
+ @click.option(
290
+ "--tap-tables-postfix", help="Postfix which is applied to standard TAP_SCHEMA table names", default=""
291
+ )
292
+ @click.option(
293
+ "--insert-metadata/--no-insert-metadata",
294
+ is_flag=True,
295
+ help="Insert metadata describing TAP_SCHEMA itself",
296
+ default=True,
297
+ )
298
+ @click.pass_context
299
+ def init_tap_schema(
300
+ ctx: click.Context, engine_url: str, tap_schema_name: str, tap_tables_postfix: str, insert_metadata: bool
301
+ ) -> None:
302
+ """Initialize a standard TAP_SCHEMA database.
303
+
304
+ Parameters
305
+ ----------
306
+ engine_url
307
+ SQLAlchemy Engine URL.
308
+ tap_schema_name
309
+ Name of the TAP_SCHEMA schema in the database.
310
+ tap_tables_postfix
311
+ Postfix which is applied to standard TAP_SCHEMA table names.
312
+ insert_metadata
313
+ Insert metadata describing TAP_SCHEMA itself.
314
+ If set to False, only the TAP_SCHEMA tables will be created, but no
315
+ metadata will be inserted.
316
+ """
317
+ url = make_url(engine_url)
318
+ engine: Engine | MockConnection
319
+ if is_mock_url(url):
320
+ raise click.ClickException("Mock engine URL is not supported for this command")
321
+ engine = create_engine(engine_url)
322
+ mgr = TableManager(
323
+ apply_schema_to_metadata=False if engine.dialect.name == "sqlite" else True,
324
+ schema_name=tap_schema_name,
325
+ table_name_postfix=tap_tables_postfix,
326
+ )
327
+ mgr.initialize_database(engine)
328
+ if insert_metadata:
329
+ inserter = MetadataInserter(mgr, engine)
330
+ inserter.insert_metadata()
331
+
332
+
418
333
  @cli.command("validate", help="Validate one or more Felis YAML files")
419
334
  @click.option(
420
335
  "--check-description", is_flag=True, help="Check that all objects have a description", default=False
@@ -493,5 +408,104 @@ def validate(
493
408
  raise click.exceptions.Exit(rc)
494
409
 
495
410
 
411
+ @cli.command(
412
+ "diff",
413
+ help="""
414
+ Compare two schemas or a schema and a database for changes
415
+
416
+ Examples:
417
+
418
+ felis diff schema1.yaml schema2.yaml
419
+
420
+ felis diff -c alembic schema1.yaml schema2.yaml
421
+
422
+ felis diff --engine-url sqlite:///test.db schema.yaml
423
+ """,
424
+ )
425
+ @click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
426
+ @click.option(
427
+ "-c",
428
+ "--comparator",
429
+ type=click.Choice(["alembic", "deepdiff"], case_sensitive=False),
430
+ help="Comparator to use for schema comparison",
431
+ default="deepdiff",
432
+ )
433
+ @click.option("-E", "--error-on-change", is_flag=True, help="Exit with error code if schemas are different")
434
+ @click.argument("files", nargs=-1, type=click.File())
435
+ @click.pass_context
436
+ def diff(
437
+ ctx: click.Context,
438
+ engine_url: str | None,
439
+ comparator: str,
440
+ error_on_change: bool,
441
+ files: Iterable[IO[str]],
442
+ ) -> None:
443
+ schemas = [
444
+ Schema.from_stream(file, context={"id_generation": ctx.obj["id_generation"]}) for file in files
445
+ ]
446
+
447
+ diff: SchemaDiff
448
+ if len(schemas) == 2 and engine_url is None:
449
+ if comparator == "alembic":
450
+ db_context = create_database(schemas[0])
451
+ assert isinstance(db_context.engine, Engine)
452
+ diff = DatabaseDiff(schemas[1], db_context.engine)
453
+ else:
454
+ diff = FormattedSchemaDiff(schemas[0], schemas[1])
455
+ elif len(schemas) == 1 and engine_url is not None:
456
+ engine = create_engine(engine_url)
457
+ diff = DatabaseDiff(schemas[0], engine)
458
+ else:
459
+ raise click.ClickException(
460
+ "Invalid arguments - provide two schemas or a schema and a database engine URL"
461
+ )
462
+
463
+ diff.print()
464
+
465
+ if diff.has_changes and error_on_change:
466
+ raise click.ClickException("Schema was changed")
467
+
468
+
469
+ @cli.command(
470
+ "dump",
471
+ help="""
472
+ Dump a schema file to YAML or JSON format
473
+
474
+ Example:
475
+
476
+ felis dump schema.yaml schema.json
477
+
478
+ felis dump schema.yaml schema_dump.yaml
479
+ """,
480
+ )
481
+ @click.option(
482
+ "--strip-ids/--no-strip-ids",
483
+ is_flag=True,
484
+ help="Strip IDs from the output schema",
485
+ default=False,
486
+ )
487
+ @click.argument("files", nargs=2, type=click.Path())
488
+ @click.pass_context
489
+ def dump(
490
+ ctx: click.Context,
491
+ strip_ids: bool,
492
+ files: list[str],
493
+ ) -> None:
494
+ if strip_ids:
495
+ logger.info("Stripping IDs from the output schema")
496
+ if files[1].endswith(".json"):
497
+ format = "json"
498
+ elif files[1].endswith(".yaml"):
499
+ format = "yaml"
500
+ else:
501
+ raise click.ClickException("Output file must have a .json or .yaml extension")
502
+ schema = Schema.from_uri(files[0], context={"id_generation": ctx.obj["id_generation"]})
503
+ with open(files[1], "w") as f:
504
+ if format == "yaml":
505
+ schema.dump_yaml(f, strip_ids=strip_ids)
506
+ elif format == "json":
507
+ schema.dump_json(f, strip_ids=strip_ids)
508
+
509
+
496
510
  if __name__ == "__main__":
497
511
  cli()
@@ -0,0 +1,33 @@
1
+ table_name,column_name,utype,ucd,unit,description,datatype,arraysize,xtype,size,principal,indexed,std,column_index
2
+ tap_schema.columns,"""size""",\N,\N,\N,deprecated: use arraysize,int,\N,\N,\N,1,0,1,9
3
+ tap_schema.columns,arraysize,\N,\N,\N,lists the size of variable-length columns in the tableset,char,16*,\N,16,1,0,1,8
4
+ tap_schema.columns,column_index,\N,\N,\N,recommended sort order when listing columns of a table,int,\N,\N,\N,1,0,1,13
5
+ tap_schema.columns,column_name,\N,\N,\N,the column name,char,64*,\N,64,1,0,1,2
6
+ tap_schema.columns,datatype,\N,\N,\N,lists the ADQL datatype of columns in the tableset,char,64*,\N,64,1,0,1,7
7
+ tap_schema.columns,description,\N,\N,\N,describes the columns in the tableset,char,512*,\N,512,1,0,1,6
8
+ tap_schema.columns,indexed,\N,\N,\N,"an indexed column; 1 means 1, 0 means 0",int,\N,\N,\N,1,0,1,11
9
+ tap_schema.columns,principal,\N,\N,\N,"a principal column; 1 means 1, 0 means 0",int,\N,\N,\N,1,0,1,10
10
+ tap_schema.columns,std,\N,\N,\N,"a standard column; 1 means 1, 0 means 0",int,\N,\N,\N,1,0,1,12
11
+ tap_schema.columns,table_name,\N,\N,\N,the table this column belongs to,char,64*,\N,64,1,0,1,1
12
+ tap_schema.columns,ucd,\N,\N,\N,lists the UCDs of columns in the tableset,char,64*,\N,64,1,0,1,4
13
+ tap_schema.columns,unit,\N,\N,\N,lists the unit used for column values in the tableset,char,64*,\N,64,1,0,1,5
14
+ tap_schema.columns,utype,\N,\N,\N,lists the utypes of columns in the tableset,char,512*,\N,512,1,0,1,3
15
+ tap_schema.columns,xtype,\N,\N,\N,a DALI or custom extended type annotation,char,64*,\N,64,1,0,1,7
16
+ tap_schema.key_columns,from_column,\N,\N,\N,column in the from_table,char,64*,\N,64,1,0,1,2
17
+ tap_schema.key_columns,key_id,\N,\N,\N,key to join to tap_schema.keys,char,64*,\N,64,1,0,1,1
18
+ tap_schema.key_columns,target_column,\N,\N,\N,column in the target_table,char,64*,\N,64,1,0,1,3
19
+ tap_schema.keys,description,\N,\N,\N,describes keys in the tableset,char,512*,\N,512,1,0,1,5
20
+ tap_schema.keys,from_table,\N,\N,\N,the table with the foreign key,char,64*,\N,64,1,0,1,2
21
+ tap_schema.keys,key_id,\N,\N,\N,unique key to join to tap_schema.key_columns,char,64*,\N,64,1,0,1,1
22
+ tap_schema.keys,target_table,\N,\N,\N,the table with the primary key,char,64*,\N,64,1,0,1,3
23
+ tap_schema.keys,utype,\N,\N,\N,lists the utype of keys in the tableset,char,512*,\N,512,1,0,1,4
24
+ tap_schema.schemas,description,\N,\N,\N,describes schemas in the tableset,char,512*,\N,512,1,0,1,3
25
+ tap_schema.schemas,schema_index,\N,\N,\N,recommended sort order when listing schemas,int,\N,\N,\N,1,0,1,4
26
+ tap_schema.schemas,schema_name,\N,\N,\N,schema name for reference to tap_schema.schemas,char,64*,\N,64,1,0,1,1
27
+ tap_schema.schemas,utype,\N,\N,\N,lists the utypes of schemas in the tableset,char,512*,\N,512,1,0,1,2
28
+ tap_schema.tables,description,\N,\N,\N,describes tables in the tableset,char,512*,\N,512,1,0,1,5
29
+ tap_schema.tables,schema_name,\N,\N,\N,the schema this table belongs to,char,512*,\N,512,1,0,1,1
30
+ tap_schema.tables,table_index,\N,\N,\N,recommended sort order when listing tables,int,\N,\N,\N,1,0,1,6
31
+ tap_schema.tables,table_name,\N,\N,\N,the fully qualified table name,char,64*,\N,64,1,0,1,2
32
+ tap_schema.tables,table_type,\N,\N,\N,one of: table view,char,8*,\N,8,1,0,1,3
33
+ tap_schema.tables,utype,\N,\N,\N,lists the utype of tables in the tableset,char,512*,\N,512,1,0,1,4
@@ -0,0 +1,8 @@
1
+ key_id,from_column,target_column
2
+ k1,schema_name,schema_name
3
+ k2,table_name,table_name
4
+ k3,from_table,table_name
5
+ k4,target_table,table_name
6
+ k5,key_id,key_id
7
+ k6,from_column,column_name
8
+ k7,target_column,column_name
@@ -0,0 +1,8 @@
1
+ key_id,from_table,target_table,utype,description
2
+ k1,tap_schema.tables,tap_schema.schemas,\N,\N
3
+ k2,tap_schema.columns,tap_schema.tables,\N,\N
4
+ k3,tap_schema.keys,tap_schema.tables,\N,\N
5
+ k4,tap_schema.keys,tap_schema.tables,\N,\N
6
+ k5,tap_schema.key_columns,tap_schema.keys,\N,\N
7
+ k6,tap_schema.key_columns,tap_schema.columns,\N,\N
8
+ k7,tap_schema.key_columns,tap_schema.columns,\N,\N
@@ -0,0 +1,2 @@
1
+ schema_name,utype,description,schema_index
2
+ tap_schema,\N,A TAP-standard-mandated schema to describe tablesets in a TAP 1.1 service,100000
@@ -0,0 +1,6 @@
1
+ schema_name,table_name,table_type,utype,description,table_index
2
+ tap_schema,tap_schema.columns,table,\N,description of columns in this tableset,102000
3
+ tap_schema,tap_schema.key_columns,table,\N,description of foreign key columns in this tableset,104000
4
+ tap_schema,tap_schema.keys,table,\N,description of foreign keys in this tableset,103000
5
+ tap_schema,tap_schema.schemas,table,\N,description of schemas in this tableset,100000
6
+ tap_schema,tap_schema.tables,table,\N,description of tables in this tableset,101000