@@ -233,6 +233,10 @@ class SpannerSQLCompiler(SQLCompiler):
233233
234234 compound_keywords = _compound_keywords
235235
236+ def __init__ (self , * args , ** kwargs ):
237+ self .tablealiases = {}
238+ super ().__init__ (* args , ** kwargs )
239+
236240 def get_from_hint_text (self , _ , text ):
237241 """Return a hint text.
238242
@@ -378,8 +382,10 @@ def limit_clause(self, select, **kw):
378382 return text
379383
380384 def returning_clause (self , stmt , returning_cols , ** kw ):
385+ # Set include_table=False because although table names are allowed in
386+ # RETURNING clauses, schema names are not.
381387 columns = [
382- self ._label_select_column (None , c , True , False , {})
388+ self ._label_select_column (None , c , True , False , {}, include_table = False )
383389 for c in expression ._select_iterables (returning_cols )
384390 ]
385391
@@ -391,6 +397,87 @@ def visit_sequence(self, seq, **kw):
391397 seq
392398 )
393399
400+ def visit_table (self , table , spanner_aliased = False , iscrud = False , ** kwargs ):
401+ """Produces the table name.
402+
403+ Schema names are not allowed in Spanner SELECT statements. We
404+ need to avoid generating SQL like
405+
406+ SELECT schema.tbl.id
407+ FROM schema.tbl
408+
409+ To do so, we alias the table in order to produce SQL like:
410+
411+ SELECT tbl_1.id, tbl_1.col
412+ FROM schema.tbl AS tbl_1
413+
414+ And do similar for UPDATE and DELETE statements.
415+
416+ We don't need to correct INSERT statements, which is fortunate
417+ because INSERT statements actually do not currently result in
418+ calls to `visit_table`.
419+
420+ This closely mirrors the mssql dialect which also avoids
421+ schema-qualified columns in SELECTs, although the behaviour is
422+ currently behind a deprecated 'legacy_schema_aliasing' flag.
423+ """
424+ if spanner_aliased is table or self .isinsert :
425+ return super ().visit_table (table , ** kwargs )
426+
427+ # alias schema-qualified tables
428+ alias = self ._schema_aliased_table (table )
429+ if alias is not None :
430+ return self .process (alias , spanner_aliased = table , ** kwargs )
431+ else :
432+ return super ().visit_table (table , ** kwargs )
433+
434+ def visit_alias (self , alias , ** kw ):
435+ """Produces alias statements."""
436+ # translate for schema-qualified table aliases
437+ kw ["spanner_aliased" ] = alias .element
438+ return super ().visit_alias (alias , ** kw )
439+
440+ def visit_column (self , column , add_to_result_map = None , ** kw ):
441+ """Produces column expressions.
442+
443+ In tandem with visit_table, replaces schema-qualified column
444+ names with column names qualified against an alias.
445+ """
446+ if (
447+ column .table is not None
448+ and not self .isinsert
449+ or self .is_subquery ()
450+ ):
451+ # translate for schema-qualified table aliases
452+ t = self ._schema_aliased_table (column .table )
453+ if t is not None :
454+ converted = elements ._corresponding_column_or_error (t , column )
455+ if add_to_result_map is not None :
456+ add_to_result_map (
457+ column .name ,
458+ column .name ,
459+ (column , column .name , column .key ),
460+ column .type ,
461+ )
462+
463+ return super ().visit_column (converted , ** kw )
464+
465+ return super ().visit_column (column , add_to_result_map = add_to_result_map , ** kw )
466+
467+ def _schema_aliased_table (self , table ):
468+ """Creates an alias for the table if it is schema-qualified.
469+
470+ If the table is schema-qualified, returns an alias for the
471+ table and caches the alias for future references to the
472+ table. If the table is not schema-qualified, returns None.
473+ """
474+ if getattr (table , "schema" , None ) is not None :
475+ if table not in self .tablealiases :
476+ self .tablealiases [table ] = table .alias ()
477+ return self .tablealiases [table ]
478+ else :
479+ return None
480+
394481
395482class SpannerDDLCompiler (DDLCompiler ):
396483 """Spanner DDL statements compiler."""
0 commit comments