Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a18f053

Browse files
committed
Fixes: #4950
add CreateTableAs for default dialect and SqLite. add Select.into constructor for CreateTableAs.
1 parent 470903b commit a18f053

File tree

6 files changed

+711
-2
lines changed

6 files changed

+711
-2
lines changed

lib/sqlalchemy/dialects/sqlite/base.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,23 @@ def get_column_specification(self, column, **kwargs):
17351735

17361736
return colspec
17371737

1738+
def visit_create_table_as(self, element, **kw):
1739+
prep = self.preparer
1740+
select_sql = self.sql_compiler.process(
1741+
element.selectable, literal_binds=True
1742+
)
1743+
1744+
parts = [
1745+
"CREATE",
1746+
"TEMPORARY" if element.temporary else None,
1747+
"TABLE",
1748+
"IF NOT EXISTS" if element.if_not_exists else None,
1749+
prep.format_table(element.table),
1750+
"AS",
1751+
select_sql,
1752+
]
1753+
return " ".join(p for p in parts if p)
1754+
17381755
def visit_primary_key_constraint(self, constraint, **kw):
17391756
# for columns with sqlite_autoincrement=True,
17401757
# the PRIMARY KEY constraint can only be inline

lib/sqlalchemy/sql/compiler.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6828,6 +6828,25 @@ def visit_create_table(self, create, **kw):
68286828
text += "\n)%s\n\n" % self.post_create_table(table)
68296829
return text
68306830

6831+
def visit_create_table_as(self, element, **kw):
6832+
"""Default CTAS emission.
6833+
6834+
Render a generic CREATE TABLE AS form and let dialects override to
6835+
add features (TEMPORARY, IF NOT EXISTS, SELECT INTO on MSSQL, etc.).
6836+
6837+
Keep **bind parameters** in the inner SELECT (no literal_binds)
6838+
"""
6839+
# target identifier (schema-qualified if present)
6840+
qualified = self.preparer.format_table(element.table)
6841+
6842+
# inner SELECT — keep binds so DDL vs DML
6843+
# differences are handled by backends
6844+
inner_kw = dict(kw)
6845+
inner_kw.pop("literal_binds", None)
6846+
select_sql = self.sql_compiler.process(element.selectable, **inner_kw)
6847+
6848+
return f"CREATE TABLE {qualified} AS {select_sql}"
6849+
68316850
def visit_create_column(self, create, first_pk=False, **kw):
68326851
column = create.element
68336852

lib/sqlalchemy/sql/ddl.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
from .base import Executable
3333
from .base import SchemaVisitor
3434
from .elements import ClauseElement
35+
from .schema import Table
36+
from .selectable import Selectable
37+
from .selectable import TableClause
3538
from .. import exc
3639
from .. import util
3740
from ..util import topological
@@ -47,8 +50,6 @@
4750
from .schema import Index
4851
from .schema import SchemaItem
4952
from .schema import Sequence as Sequence # noqa: F401
50-
from .schema import Table
51-
from .selectable import TableClause
5253
from ..engine.base import Connection
5354
from ..engine.interfaces import CacheStats
5455
from ..engine.interfaces import CompiledCacheType
@@ -544,6 +545,93 @@ def __init__(
544545
self.include_foreign_key_constraints = include_foreign_key_constraints
545546

546547

548+
class CreateTableAs(ExecutableDDLElement):
549+
"""Represent a CREATE TABLE ... AS (CTAS) statement.
550+
551+
This creates a new table directly from the output of a SELECT.
552+
The set of columns in the new table is derived from the
553+
SELECT list; constraints, indexes, and defaults are not copied.
554+
555+
:param selectable: :class:`_sql.Selectable`
556+
The SELECT (or other selectable) providing the columns and rows.
557+
558+
:param target: str | :class:`_sql.TableClause`
559+
Table name or object. If passed as a string, it must be
560+
unqualified; use the ``schema`` argument for qualification.
561+
562+
:param schema: str, optional
563+
Schema or owner name. If both ``schema`` and the target object
564+
specify a schema, they must match.
565+
566+
:param temporary: bool, default False.
567+
If True, render ``TEMPORARY`` (PostgreSQL, MySQL, SQLite), or
568+
a ``#<name>`` temporary table on SQL Server. Dialects that do
569+
not support this option will raise :class:`.CompileError`.
570+
571+
:param if_not_exists: bool, default False.
572+
If True, render ``IF NOT EXISTS`` where supported
573+
(PostgreSQL, MySQL, SQLite). Dialects that do not support this
574+
option will raise :class:`.CompileError`.
575+
"""
576+
577+
__visit_name__ = "create_table_as"
578+
inherit_cache = False
579+
580+
def __init__(
581+
self,
582+
selectable: Selectable,
583+
target: Union[str, TableClause],
584+
*,
585+
schema: Optional[str] = None,
586+
temporary: bool = False,
587+
if_not_exists: bool = False,
588+
):
589+
if isinstance(target, TableClause):
590+
t_name = target.name
591+
t_schema = target.schema
592+
593+
if not t_name or not str(t_name).strip():
594+
raise exc.ArgumentError("Table name must be non-empty")
595+
596+
if (
597+
schema is not None
598+
and t_schema is not None
599+
and schema != t_schema
600+
):
601+
raise exc.ArgumentError(
602+
f"Conflicting schema: target={t_schema!r}, "
603+
f"schema={schema!r}"
604+
)
605+
final_schema = (
606+
schema
607+
if (schema is not None and t_schema is None)
608+
else t_schema
609+
)
610+
elif isinstance(target, str):
611+
if not target.strip():
612+
raise exc.ArgumentError("Table name must be non-empty")
613+
if "." in target:
614+
raise exc.ArgumentError(
615+
"Target string must be unqualified (use schema=)."
616+
)
617+
t_name = target
618+
final_schema = schema
619+
else:
620+
raise exc.ArgumentError("target must be a string, TableClause")
621+
622+
self.table = TableClause(t_name, schema=final_schema)
623+
624+
self.target = target
625+
self.schema = final_schema
626+
self.selectable = selectable
627+
self.temporary = bool(temporary)
628+
self.if_not_exists = bool(if_not_exists)
629+
630+
@property
631+
def generated_table(self) -> TableClause:
632+
return self.table
633+
634+
547635
class _DropView(_DropBase["Table"]):
548636
"""Semi-public 'DROP VIEW' construct.
549637

lib/sqlalchemy/sql/selectable.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
from .base import ReadOnlyColumnCollection
139139
from .cache_key import _CacheKeyTraversalType
140140
from .compiler import SQLCompiler
141+
from .ddl import CreateTableAs
141142
from .dml import Delete
142143
from .dml import Update
143144
from .elements import BinaryExpression
@@ -148,6 +149,7 @@
148149
from .functions import Function
149150
from .schema import ForeignKey
150151
from .schema import ForeignKeyConstraint
152+
from .schema import Table
151153
from .sqltypes import TableValueType
152154
from .type_api import TypeEngine
153155
from .visitors import _CloneCallableType
@@ -6813,6 +6815,24 @@ def intersect_all(
68136815
"""
68146816
return CompoundSelect._create_intersect_all(self, *other)
68156817

6818+
def into(
6819+
self,
6820+
target: Union[str, TableClause, Table],
6821+
*,
6822+
schema: Optional[str] = None,
6823+
temporary: bool = False,
6824+
if_not_exists: bool = False,
6825+
) -> "CreateTableAs":
6826+
from .ddl import CreateTableAs
6827+
6828+
return CreateTableAs(
6829+
self,
6830+
target,
6831+
schema=schema,
6832+
temporary=temporary,
6833+
if_not_exists=if_not_exists,
6834+
)
6835+
68166836

68176837
class ScalarSelect(
68186838
roles.InElementRole, Generative, GroupedElement, ColumnElement[_T]

0 commit comments

Comments
 (0)