You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
lineage with schema info is the wrong software engineering pattern #8610
The idea in the recent release seems to be that you provide the schemas in the lineage() method, so that the source data types can be derived automatically. Good idea, but...
In order to know what schemas to add, I must parse the SQL. Or I add all 10'000 tables of my database because I don't know the SQL statement.
Hence I would suggest two things:
Schema is an optional parameter
Do not provide a schema but a getter function. When the lineage requires a table schema information, it should call a function that I have to provide, which returns the Schema/dict object.
We can't do this. Schema was made mandatory because otherwise qualify() was unreliable, and hence lineage(), too. For example, in SELECT a FROM t JOIN u ON t.id = u.id, a can mean either t.a or u.a.
This seems unnecessary. The existing Schema interface already exposes column_names, get_column_type and the lineage() function can accept arbitrary implementations of this interface. You can thus create an implementation that lazily fetches metadata on demand and caches it.
The input sql is parsed regardless, btw. If you want to do that outside of the lineage() call to analyze the scanned tables, you can do that and then just pass the parsed expression to it with copy=False to avoid the extra overhead.
Say we have a SQL like SELECT t.id FROM t JOIN u ON t.id = u.id, how would I implement a Schema class that loads the tables t and u from the database dictionary via a select statement. Just the part of how to invoke a get_table(tablename)?
I wonder if we talk about the same thing. I am not saying schema as in qualifier. I meant your test cases like
node = lineage(
"a",
"SELECT a FROM z",
schema={"x": {"a": "int"}},
sources={"y": "SELECT * FROM x", "z": "SELECT a FROM y"},
)
I cannot provide the dict/Schema of 10'000 tables just in case the SQL is using any of those. So must be loaded lazy. But Schema is derived from Expression and Expression I do not see any hook for lazy loading.
Your example 1) is easy to answer. That is an invalid SQL syntax.
Just FYI, I am stuck now. The previous version was working without the schema information and the current requires it. Without the schema info it fails, adding an empty dict raises an error also. And how to provide the Schema, I don't know yet.
Your example 1) is easy to answer. That is an invalid SQL syntax.
Not if u doesn't contain the a column; you can't tell based only on the query's text. For example, this is valid:
memory D create or replace table t(id, a) as select 1, 'foo';
memory D create or replace table u(id) as select 1;
memory D select a from t join u on t.id = u.id;
┌─────────┐
│ a │
│ varchar │
├─────────┤
│ foo │
└─────────┘
Anyway, I was alluding to something that looks like this:
classLazySchema(MappingSchema):
def__init__(self, get_table, dialect=None):
super().__init__(dialect=dialect)
self.get_table=get_tableself.loaded=set()
def_load(self, table):
table=exp.to_table(table, dialect=self.dialect)
name=table.nameifnamenotinself.loaded:
columns=self.get_table(name) # e.g. {"id": "INT"}ifcolumnsisNone:
raiseValueError(f"Missing metadata for {name}")
self.add_table(table, columns)
self.loaded.add(name)
defcolumn_names(self, table, only_visible=False, **kwargs):
self._load(table)
returnsuper().column_names(
table, only_visible=only_visible, **kwargs
)
defget_column_type(self, table, column, **kwargs):
self._load(table)
returnsuper().get_column_type(table, column, **kwargs)
defget_table(name):
# Query the database metadata using a bound table-name parameter.# Return {column_name: sql_type} in database column order.
...
And then you do:
node=lineage(
"id",
"SELECT t.id FROM t JOIN u ON t.id = u.id",
schema=LazySchema(get_table),
)
Unfortunately I have one more. In Windows and Python 3.11.9 it works. When executed as github action, meaning Ubuntu, same version, it says at schema=LazySchema(get_table, dialect=Fabric)
TypeError: interpreted classes cannot inherit from compiled
I assume this is related to sqlglot[c] vs sqlglot, but don't know and it certainly is above my knowledge. Any hints would be greatly appreciated.
Activity
georgesittas commentedon Oct 12, 2026
qualify()was unreliable, and hencelineage(), too. For example, inSELECT a FROM t JOIN u ON t.id = u.id,acan mean eithert.aoru.a.Schemainterface already exposescolumn_names,get_column_typeand thelineage()function can accept arbitrary implementations of this interface. You can thus create an implementation that lazily fetches metadata on demand and caches it.The input
sqlis parsed regardless, btw. If you want to do that outside of thelineage()call to analyze the scanned tables, you can do that and then just pass the parsed expression to it withcopy=Falseto avoid the extra overhead.wernerdaehn commentedon Oct 12, 2026
Do you have a quick example of the lazy loading?
Say we have a SQL like
SELECT t.id FROM t JOIN u ON t.id = u.id, how would I implement aSchemaclass that loads the tablestandufrom the database dictionary via a select statement. Just the part of how to invoke aget_table(tablename)?I wonder if we talk about the same thing. I am not saying schema as in qualifier. I meant your test cases like
I cannot provide the dict/Schema of 10'000 tables just in case the SQL is using any of those. So must be loaded lazy. But Schema is derived from Expression and Expression I do not see any hook for lazy loading.
Your example 1) is easy to answer. That is an invalid SQL syntax.
wernerdaehn commentedon Oct 12, 2026
Just FYI, I am stuck now. The previous version was working without the schema information and the current requires it. Without the schema info it fails, adding an empty dict raises an error also. And how to provide the Schema, I don't know yet.
georgesittas commentedon Oct 12, 2026
Not if
udoesn't contain theacolumn; you can't tell based only on the query's text. For example, this is valid:Anyway, I was alluding to something that looks like this:
And then you do:
wernerdaehn commentedon Oct 13, 2026
Right, forgot about the case where
ais an unique identifier across all tables.Thanks for the sample, will implement it.
wernerdaehn commentedon Oct 13, 2026
Success. Thanks for your help, appreciate it!
wernerdaehn commentedon Oct 14, 2026
Info: When you pass a dialect into lineage, also pass it to the LazySchema.
Else below returns an error that USER_NAME cannot be found. Of course, different dialects use different case sensitivity.
wernerdaehn commentedon Oct 15, 2026
Unfortunately I have one more. In Windows and Python 3.11.9 it works. When executed as github action, meaning Ubuntu, same version, it says at
schema=LazySchema(get_table, dialect=Fabric)TypeError: interpreted classes cannot inherit from compiledI assume this is related to sqlglot[c] vs sqlglot, but don't know and it certainly is above my knowledge. Any hints would be greatly appreciated.
Yes, I have seen #7700
georgesittas commentedon Oct 15, 2026
You're using an older sqlglot version I think,
schema.pyis no longer a compiled module IIRC.wernerdaehn commentedon Oct 15, 2026
Yes, there seems to have been some caching going on in the private github action runner.