instructure-pysqlsync 0.8.5.dev0__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.
- instructure_pysqlsync-0.8.5.dev0.dist-info/METADATA +244 -0
- instructure_pysqlsync-0.8.5.dev0.dist-info/RECORD +102 -0
- instructure_pysqlsync-0.8.5.dev0.dist-info/WHEEL +5 -0
- instructure_pysqlsync-0.8.5.dev0.dist-info/licenses/LICENSE +21 -0
- instructure_pysqlsync-0.8.5.dev0.dist-info/top_level.txt +1 -0
- instructure_pysqlsync-0.8.5.dev0.dist-info/zip-safe +1 -0
- pysqlsync/__init__.py +15 -0
- pysqlsync/base.py +1386 -0
- pysqlsync/connection.py +101 -0
- pysqlsync/data/__init__.py +0 -0
- pysqlsync/data/exchange.py +184 -0
- pysqlsync/data/generator.py +305 -0
- pysqlsync/dialect/README.md +35 -0
- pysqlsync/dialect/__init__.py +0 -0
- pysqlsync/dialect/delta/__init__.py +0 -0
- pysqlsync/dialect/delta/data_types.py +51 -0
- pysqlsync/dialect/delta/dependency.py +7 -0
- pysqlsync/dialect/delta/engine.py +18 -0
- pysqlsync/dialect/delta/generator.py +74 -0
- pysqlsync/dialect/delta/object_types.py +87 -0
- pysqlsync/dialect/mssql/README.md +23 -0
- pysqlsync/dialect/mssql/__init__.py +0 -0
- pysqlsync/dialect/mssql/connection.py +146 -0
- pysqlsync/dialect/mssql/data_types.py +113 -0
- pysqlsync/dialect/mssql/dependency.py +9 -0
- pysqlsync/dialect/mssql/discovery.py +99 -0
- pysqlsync/dialect/mssql/engine.py +20 -0
- pysqlsync/dialect/mssql/generator.py +88 -0
- pysqlsync/dialect/mssql/mutation.py +46 -0
- pysqlsync/dialect/mssql/object_types.py +135 -0
- pysqlsync/dialect/mysql/__init__.py +0 -0
- pysqlsync/dialect/mysql/connection.py +126 -0
- pysqlsync/dialect/mysql/data_types.py +40 -0
- pysqlsync/dialect/mysql/dependency.py +10 -0
- pysqlsync/dialect/mysql/discovery.py +145 -0
- pysqlsync/dialect/mysql/engine.py +20 -0
- pysqlsync/dialect/mysql/generator.py +140 -0
- pysqlsync/dialect/mysql/mutation.py +65 -0
- pysqlsync/dialect/mysql/object_types.py +75 -0
- pysqlsync/dialect/oracle/README.md +22 -0
- pysqlsync/dialect/oracle/__init__.py +0 -0
- pysqlsync/dialect/oracle/connection.py +141 -0
- pysqlsync/dialect/oracle/data_types.py +104 -0
- pysqlsync/dialect/oracle/dependency.py +9 -0
- pysqlsync/dialect/oracle/discovery.py +231 -0
- pysqlsync/dialect/oracle/engine.py +20 -0
- pysqlsync/dialect/oracle/generator.py +93 -0
- pysqlsync/dialect/oracle/mutation.py +37 -0
- pysqlsync/dialect/oracle/object_types.py +59 -0
- pysqlsync/dialect/postgresql/__init__.py +0 -0
- pysqlsync/dialect/postgresql/connection.py +133 -0
- pysqlsync/dialect/postgresql/data_types.py +6 -0
- pysqlsync/dialect/postgresql/dependency.py +9 -0
- pysqlsync/dialect/postgresql/discovery.py +392 -0
- pysqlsync/dialect/postgresql/engine.py +20 -0
- pysqlsync/dialect/postgresql/generator.py +99 -0
- pysqlsync/dialect/postgresql/mutation.py +82 -0
- pysqlsync/dialect/postgresql/object_types.py +99 -0
- pysqlsync/dialect/redshift/__init__.py +0 -0
- pysqlsync/dialect/redshift/connection.py +156 -0
- pysqlsync/dialect/redshift/data_types.py +10 -0
- pysqlsync/dialect/redshift/dependency.py +9 -0
- pysqlsync/dialect/redshift/engine.py +20 -0
- pysqlsync/dialect/redshift/generator.py +82 -0
- pysqlsync/dialect/snowflake/__init__.py +0 -0
- pysqlsync/dialect/snowflake/data_types.py +18 -0
- pysqlsync/dialect/snowflake/dependency.py +9 -0
- pysqlsync/dialect/snowflake/engine.py +18 -0
- pysqlsync/dialect/snowflake/generator.py +64 -0
- pysqlsync/dialect/snowflake/object_types.py +84 -0
- pysqlsync/dialect/test/__init__.py +0 -0
- pysqlsync/dialect/test/dependency.py +10 -0
- pysqlsync/dialect/trino/__init__.py +0 -0
- pysqlsync/dialect/trino/connection.py +86 -0
- pysqlsync/dialect/trino/dependency.py +9 -0
- pysqlsync/dialect/trino/discovery.py +21 -0
- pysqlsync/dialect/trino/engine.py +20 -0
- pysqlsync/dialect/trino/generator.py +31 -0
- pysqlsync/factory.py +169 -0
- pysqlsync/formation/__init__.py +0 -0
- pysqlsync/formation/constraints.py +78 -0
- pysqlsync/formation/data_types.py +192 -0
- pysqlsync/formation/discovery.py +390 -0
- pysqlsync/formation/inspection.py +179 -0
- pysqlsync/formation/mutation.py +320 -0
- pysqlsync/formation/object_dict.py +79 -0
- pysqlsync/formation/object_types.py +872 -0
- pysqlsync/formation/py_to_sql.py +1134 -0
- pysqlsync/formation/sql_to_py.py +194 -0
- pysqlsync/model/__init__.py +0 -0
- pysqlsync/model/data_types.py +397 -0
- pysqlsync/model/entity_types.py +68 -0
- pysqlsync/model/id_types.py +193 -0
- pysqlsync/model/key_types.py +38 -0
- pysqlsync/model/properties.py +234 -0
- pysqlsync/py.typed +0 -0
- pysqlsync/python_types.py +192 -0
- pysqlsync/resultset.py +106 -0
- pysqlsync/util/__init__.py +0 -0
- pysqlsync/util/dataclasses.py +80 -0
- pysqlsync/util/dispatch.py +28 -0
- pysqlsync/util/typing.py +13 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import ipaddress
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from strong_typing.auxiliary import int8, int16, int32, int64
|
|
7
|
+
from strong_typing.core import JsonType
|
|
8
|
+
|
|
9
|
+
from pysqlsync.base import BaseGenerator, GeneratorOptions
|
|
10
|
+
from pysqlsync.dialect.oracle.mutation import OracleMutator
|
|
11
|
+
from pysqlsync.formation.inspection import is_ip_address_type
|
|
12
|
+
from pysqlsync.formation.object_types import Column
|
|
13
|
+
from pysqlsync.formation.py_to_sql import ArrayMode, DataclassConverter, DataclassConverterOptions, EnumMode, NamespaceMapping, StructMode
|
|
14
|
+
from pysqlsync.util.typing import override
|
|
15
|
+
|
|
16
|
+
from .data_types import OracleIntegerType, OracleTimestampType, OracleTimeType, OracleVariableBinaryType, OracleVariableCharacterType
|
|
17
|
+
from .object_types import OracleObjectFactory
|
|
18
|
+
|
|
19
|
+
MIN_DATETIME = datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
|
|
20
|
+
MIN_DATE = datetime.date.min
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OracleGenerator(BaseGenerator):
|
|
24
|
+
"Generator for Oracle."
|
|
25
|
+
|
|
26
|
+
converter: DataclassConverter
|
|
27
|
+
|
|
28
|
+
def __init__(self, options: GeneratorOptions) -> None:
|
|
29
|
+
super().__init__(options, OracleObjectFactory(), OracleMutator(options.synchronization))
|
|
30
|
+
|
|
31
|
+
self.check_enum_mode(exclude=[EnumMode.TYPE, EnumMode.INLINE])
|
|
32
|
+
self.check_struct_mode(matches=StructMode.JSON)
|
|
33
|
+
self.check_array_mode(include=[ArrayMode.JSON, ArrayMode.RELATION])
|
|
34
|
+
|
|
35
|
+
self.converter = DataclassConverter(
|
|
36
|
+
options=DataclassConverterOptions(
|
|
37
|
+
enum_mode=options.enum_mode or EnumMode.RELATION,
|
|
38
|
+
struct_mode=options.struct_mode or StructMode.JSON,
|
|
39
|
+
array_mode=options.array_mode or ArrayMode.JSON,
|
|
40
|
+
qualified_names=False,
|
|
41
|
+
namespaces=NamespaceMapping(options.namespaces),
|
|
42
|
+
foreign_constraints=options.foreign_constraints,
|
|
43
|
+
initialize_tables=options.initialize_tables,
|
|
44
|
+
substitutions={
|
|
45
|
+
bytes: OracleVariableBinaryType(),
|
|
46
|
+
datetime.time: OracleTimeType(),
|
|
47
|
+
datetime.datetime: OracleTimestampType(),
|
|
48
|
+
int: OracleIntegerType(),
|
|
49
|
+
int8: OracleIntegerType(),
|
|
50
|
+
int16: OracleIntegerType(),
|
|
51
|
+
int32: OracleIntegerType(),
|
|
52
|
+
int64: OracleIntegerType(),
|
|
53
|
+
str: OracleVariableCharacterType(),
|
|
54
|
+
uuid.UUID: OracleVariableBinaryType(16),
|
|
55
|
+
JsonType: OracleVariableCharacterType(),
|
|
56
|
+
ipaddress.IPv4Address: OracleVariableBinaryType(4),
|
|
57
|
+
ipaddress.IPv6Address: OracleVariableBinaryType(16),
|
|
58
|
+
},
|
|
59
|
+
factory=self.factory,
|
|
60
|
+
skip_annotations=options.skip_annotations,
|
|
61
|
+
auto_default=options.auto_default,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
def get_current_schema_stmt(self) -> str:
|
|
67
|
+
return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')"
|
|
68
|
+
|
|
69
|
+
@override
|
|
70
|
+
def placeholder(self, index: int) -> str:
|
|
71
|
+
return f":{index}"
|
|
72
|
+
|
|
73
|
+
@override
|
|
74
|
+
def get_field_extractor(self, column: Column, field_name: str, field_type: type) -> Callable[[Any], Any]:
|
|
75
|
+
if field_type is uuid.UUID:
|
|
76
|
+
return lambda obj: getattr(obj, field_name).bytes
|
|
77
|
+
elif is_ip_address_type(field_type):
|
|
78
|
+
return lambda obj: getattr(obj, field_name).packed
|
|
79
|
+
elif field_type is datetime.time:
|
|
80
|
+
return lambda obj: datetime.datetime.combine(MIN_DATE, getattr(obj, field_name)) - MIN_DATETIME
|
|
81
|
+
|
|
82
|
+
return super().get_field_extractor(column, field_name, field_type)
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
def get_value_transformer(self, column: Column, field_type: type) -> Optional[Callable[[Any], Any]]:
|
|
86
|
+
if field_type is uuid.UUID:
|
|
87
|
+
return lambda field: field.bytes
|
|
88
|
+
elif is_ip_address_type(field_type):
|
|
89
|
+
return lambda field: field.packed
|
|
90
|
+
elif field_type is datetime.time:
|
|
91
|
+
return lambda field: datetime.datetime.combine(datetime.date.min, field) - datetime.datetime.min
|
|
92
|
+
|
|
93
|
+
return super().get_value_transformer(column, field_type)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pysqlsync.formation.mutation import Mutator
|
|
5
|
+
from pysqlsync.formation.object_types import Column
|
|
6
|
+
|
|
7
|
+
from .object_types import OracleColumn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OracleMutator(Mutator):
|
|
11
|
+
def mutate_column_stmt(self, source: Column, target: Column) -> Optional[str]:
|
|
12
|
+
source_column = typing.cast(OracleColumn, source)
|
|
13
|
+
target_column = typing.cast(OracleColumn, target)
|
|
14
|
+
|
|
15
|
+
changes: list[str] = []
|
|
16
|
+
if source_column.data_type != target_column.data_type:
|
|
17
|
+
changes.append(str(target_column.data_type))
|
|
18
|
+
if source_column.default != target_column.default:
|
|
19
|
+
if target_column.default is not None:
|
|
20
|
+
changes.append(f"DEFAULT {target_column.default_expr}")
|
|
21
|
+
else:
|
|
22
|
+
changes.append("DEFAULT NULL")
|
|
23
|
+
if source_column.nullable != target_column.nullable:
|
|
24
|
+
if not target_column.nullable:
|
|
25
|
+
changes.append("NOT NULL")
|
|
26
|
+
else:
|
|
27
|
+
changes.append("NULL")
|
|
28
|
+
if source_column.identity != target_column.identity:
|
|
29
|
+
if target_column.identity:
|
|
30
|
+
changes.append("GENERATED BY DEFAULT AS IDENTITY")
|
|
31
|
+
else:
|
|
32
|
+
changes.append("DROP IDENTITY")
|
|
33
|
+
|
|
34
|
+
if changes:
|
|
35
|
+
return f"MODIFY {source_column.name} {' '.join(changes)}"
|
|
36
|
+
else:
|
|
37
|
+
return None
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from pysqlsync.formation.object_types import Column, EnumTable, ObjectFactory, Table
|
|
4
|
+
from pysqlsync.model.data_types import SqlTimestampType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OracleColumn(Column):
|
|
8
|
+
@property
|
|
9
|
+
def default_expr(self) -> str:
|
|
10
|
+
if self.default is None:
|
|
11
|
+
raise ValueError("default value is NULL")
|
|
12
|
+
|
|
13
|
+
if isinstance(self.data_type, SqlTimestampType):
|
|
14
|
+
m = re.match(
|
|
15
|
+
r"^'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}) (?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})'$",
|
|
16
|
+
self.default,
|
|
17
|
+
)
|
|
18
|
+
if m:
|
|
19
|
+
return f"TIMESTAMP {self.default}"
|
|
20
|
+
|
|
21
|
+
return self.default
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def data_spec(self) -> str:
|
|
25
|
+
default = f" DEFAULT {self.default_expr}" if self.default is not None else ""
|
|
26
|
+
nullable = " NOT NULL" if not self.nullable and not self.identity else ""
|
|
27
|
+
identity = " GENERATED BY DEFAULT AS IDENTITY" if self.identity else ""
|
|
28
|
+
return f"{self.data_type}{default}{nullable}{identity}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OracleTable(Table):
|
|
32
|
+
def alter_table_stmt(self, statements: list[str]) -> str:
|
|
33
|
+
return "\n".join(f"ALTER TABLE {self.name} {statement};" for statement in statements)
|
|
34
|
+
|
|
35
|
+
def drop_if_exists_stmt(self) -> str:
|
|
36
|
+
return (
|
|
37
|
+
"BEGIN\n"
|
|
38
|
+
f" EXECUTE IMMEDIATE 'DROP TABLE {self.name} CASCADE CONSTRAINTS PURGE';\n"
|
|
39
|
+
"EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF;\n"
|
|
40
|
+
"END;"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OracleEnumTable(EnumTable, OracleTable):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OracleObjectFactory(ObjectFactory):
|
|
49
|
+
@property
|
|
50
|
+
def column_class(self) -> type[Column]:
|
|
51
|
+
return OracleColumn
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def table_class(self) -> type[Table]:
|
|
55
|
+
return OracleTable
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def enum_table_class(self) -> type[EnumTable]:
|
|
59
|
+
return OracleEnumTable
|
|
File without changes
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import logging
|
|
3
|
+
import ssl
|
|
4
|
+
import typing
|
|
5
|
+
from typing import Iterable, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
import asyncpg
|
|
8
|
+
from strong_typing.inspection import DataclassInstance, is_dataclass_type
|
|
9
|
+
|
|
10
|
+
from pysqlsync.base import BaseConnection, BaseContext, ClassRef, DataSource
|
|
11
|
+
from pysqlsync.connection import ConnectionSSLMode, create_context
|
|
12
|
+
from pysqlsync.formation.object_types import Table
|
|
13
|
+
from pysqlsync.model.properties import is_identity_type
|
|
14
|
+
from pysqlsync.resultset import resultset_unwrap_dict, resultset_unwrap_tuple
|
|
15
|
+
from pysqlsync.util.typing import override
|
|
16
|
+
|
|
17
|
+
D = TypeVar("D", bound=DataclassInstance)
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
LOGGER = logging.getLogger("pysqlsync.postgres")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PostgreSQLConnection(BaseConnection):
|
|
24
|
+
native: asyncpg.Connection
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
async def open(self) -> BaseContext:
|
|
28
|
+
LOGGER.info("connecting to %s", self.params)
|
|
29
|
+
|
|
30
|
+
ssl_mode = self.params.ssl
|
|
31
|
+
if ssl_mode is None or ssl_mode is ConnectionSSLMode.disable:
|
|
32
|
+
return await self._open()
|
|
33
|
+
elif ssl_mode is ConnectionSSLMode.prefer:
|
|
34
|
+
try:
|
|
35
|
+
return await self._open(create_context(ssl_mode))
|
|
36
|
+
except ConnectionError:
|
|
37
|
+
return await self._open()
|
|
38
|
+
elif ssl_mode is ConnectionSSLMode.allow:
|
|
39
|
+
try:
|
|
40
|
+
return await self._open()
|
|
41
|
+
except ConnectionError:
|
|
42
|
+
return await self._open(create_context(ssl_mode))
|
|
43
|
+
elif ssl_mode is ConnectionSSLMode.require or ssl_mode is ConnectionSSLMode.verify_ca or ssl_mode is ConnectionSSLMode.verify_full:
|
|
44
|
+
return await self._open(create_context(ssl_mode))
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"unsupported SSL mode: {ssl_mode}")
|
|
47
|
+
|
|
48
|
+
async def _open(self, ctx: Optional[ssl.SSLContext] = None) -> BaseContext:
|
|
49
|
+
conn = await asyncpg.connect(
|
|
50
|
+
host=self.params.host,
|
|
51
|
+
port=self.params.port,
|
|
52
|
+
user=self.params.username,
|
|
53
|
+
password=self.params.password,
|
|
54
|
+
database=self.params.database,
|
|
55
|
+
ssl=ctx,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
ver = conn.get_server_version()
|
|
59
|
+
LOGGER.info(
|
|
60
|
+
"PostgreSQL version %d.%d.%d %s",
|
|
61
|
+
ver.major,
|
|
62
|
+
ver.minor,
|
|
63
|
+
ver.micro,
|
|
64
|
+
ver.releaselevel,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self.native = conn
|
|
68
|
+
return PostgreSQLContext(self)
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
async def close(self) -> None:
|
|
72
|
+
await self.native.close()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class PostgreSQLContext(BaseContext):
|
|
76
|
+
def __init__(self, connection: PostgreSQLConnection) -> None:
|
|
77
|
+
super().__init__(connection)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def native_connection(self) -> asyncpg.Connection:
|
|
81
|
+
return typing.cast(PostgreSQLConnection, self.connection).native
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
async def _execute(self, statement: str) -> None:
|
|
85
|
+
await self.native_connection.execute(statement)
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
async def _execute_all(self, statement: str, source: DataSource) -> None:
|
|
89
|
+
async for batch in source.batches():
|
|
90
|
+
await self.native_connection.executemany(statement, batch)
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
async def _query_all(self, signature: type[T], statement: str) -> list[T]:
|
|
94
|
+
records: list[asyncpg.Record] = await self.native_connection.fetch(statement)
|
|
95
|
+
if is_dataclass_type(signature):
|
|
96
|
+
return resultset_unwrap_dict(signature, records) # type: ignore
|
|
97
|
+
else:
|
|
98
|
+
return resultset_unwrap_tuple(signature, records) # type: ignore
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
async def insert_data(self, table: type[D], data: Iterable[D]) -> None:
|
|
102
|
+
if not is_dataclass_type(table):
|
|
103
|
+
raise TypeError(f"expected dataclass type, got: {table}")
|
|
104
|
+
generator = self.connection.generator
|
|
105
|
+
table_name = generator.get_qualified_id(ClassRef(table))
|
|
106
|
+
records = generator.get_dataclasses_as_records(table, data, skip_identity=True)
|
|
107
|
+
result = await self.native_connection.copy_records_to_table(
|
|
108
|
+
schema_name=table_name.scope_id,
|
|
109
|
+
table_name=table_name.local_id,
|
|
110
|
+
columns=tuple(field.name for field in dataclasses.fields(table) if not is_identity_type(field.type)),
|
|
111
|
+
records=records,
|
|
112
|
+
)
|
|
113
|
+
LOGGER.debug(result)
|
|
114
|
+
|
|
115
|
+
@override
|
|
116
|
+
async def _insert_rows(
|
|
117
|
+
self,
|
|
118
|
+
table: Table,
|
|
119
|
+
source: DataSource,
|
|
120
|
+
*,
|
|
121
|
+
field_types: tuple[type, ...],
|
|
122
|
+
field_names: Optional[tuple[str, ...]] = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
record_generator = await self._generate_records(table, source, field_types=field_types, field_names=field_names)
|
|
125
|
+
order = tuple(name for name in field_names if name) if field_names else None
|
|
126
|
+
async for batch in record_generator.batches():
|
|
127
|
+
result = await self.native_connection.copy_records_to_table(
|
|
128
|
+
schema_name=table.name.scope_id,
|
|
129
|
+
table_name=table.name.local_id,
|
|
130
|
+
columns=[str(col.name.local_id) for col in table.get_columns(order)],
|
|
131
|
+
records=batch,
|
|
132
|
+
)
|
|
133
|
+
LOGGER.debug(result)
|