snowflake-sqlalchemy 1.7.3__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.
- snowflake/sqlalchemy/__init__.py +162 -0
- snowflake/sqlalchemy/_constants.py +14 -0
- snowflake/sqlalchemy/base.py +1188 -0
- snowflake/sqlalchemy/compat.py +36 -0
- snowflake/sqlalchemy/custom_commands.py +627 -0
- snowflake/sqlalchemy/custom_types.py +155 -0
- snowflake/sqlalchemy/exc.py +82 -0
- snowflake/sqlalchemy/functions.py +16 -0
- snowflake/sqlalchemy/parser/custom_type_parser.py +245 -0
- snowflake/sqlalchemy/provision.py +12 -0
- snowflake/sqlalchemy/requirements.py +313 -0
- snowflake/sqlalchemy/snowdialect.py +1029 -0
- snowflake/sqlalchemy/sql/__init__.py +3 -0
- snowflake/sqlalchemy/sql/custom_schema/__init__.py +9 -0
- snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +37 -0
- snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +127 -0
- snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +13 -0
- snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +117 -0
- snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +63 -0
- snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +102 -0
- snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +33 -0
- snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +63 -0
- snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +58 -0
- snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +63 -0
- snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +25 -0
- snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +65 -0
- snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +14 -0
- snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +67 -0
- snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +84 -0
- snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +94 -0
- snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +70 -0
- snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +54 -0
- snowflake/sqlalchemy/util.py +344 -0
- snowflake/sqlalchemy/version.py +6 -0
- snowflake_sqlalchemy-1.7.3.dist-info/METADATA +737 -0
- snowflake_sqlalchemy-1.7.3.dist-info/RECORD +39 -0
- snowflake_sqlalchemy-1.7.3.dist-info/WHEEL +4 -0
- snowflake_sqlalchemy-1.7.3.dist-info/entry_points.txt +2 -0
- snowflake_sqlalchemy-1.7.3.dist-info/licenses/LICENSE.txt +202 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
from typing import Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
import sqlalchemy.types as sqltypes
|
|
7
|
+
import sqlalchemy.util as util
|
|
8
|
+
from sqlalchemy.types import TypeEngine
|
|
9
|
+
|
|
10
|
+
TEXT = sqltypes.VARCHAR
|
|
11
|
+
CHARACTER = sqltypes.CHAR
|
|
12
|
+
DEC = sqltypes.DECIMAL
|
|
13
|
+
DOUBLE = sqltypes.FLOAT
|
|
14
|
+
FIXED = sqltypes.DECIMAL
|
|
15
|
+
NUMBER = sqltypes.DECIMAL
|
|
16
|
+
BYTEINT = sqltypes.SMALLINT
|
|
17
|
+
STRING = sqltypes.VARCHAR
|
|
18
|
+
TINYINT = sqltypes.SMALLINT
|
|
19
|
+
VARBINARY = sqltypes.BINARY
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _process_float(value):
|
|
23
|
+
if value == float("inf"):
|
|
24
|
+
return "inf"
|
|
25
|
+
elif value == float("-inf"):
|
|
26
|
+
return "-inf"
|
|
27
|
+
elif value is not None:
|
|
28
|
+
return float(value)
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SnowflakeType(sqltypes.TypeEngine):
|
|
33
|
+
def _default_dialect(self):
|
|
34
|
+
# Get around circular import
|
|
35
|
+
return __import__("snowflake.sqlalchemy").sqlalchemy.dialect()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class VARIANT(SnowflakeType):
|
|
39
|
+
__visit_name__ = "VARIANT"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StructuredType(SnowflakeType):
|
|
43
|
+
def __init__(self, is_semi_structured: bool = False):
|
|
44
|
+
self.is_semi_structured = is_semi_structured
|
|
45
|
+
super().__init__()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MAP(StructuredType):
|
|
49
|
+
__visit_name__ = "MAP"
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
key_type: sqltypes.TypeEngine,
|
|
54
|
+
value_type: sqltypes.TypeEngine,
|
|
55
|
+
not_null: bool = False,
|
|
56
|
+
):
|
|
57
|
+
self.key_type = key_type
|
|
58
|
+
self.value_type = value_type
|
|
59
|
+
self.not_null = not_null
|
|
60
|
+
super().__init__()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class OBJECT(StructuredType):
|
|
64
|
+
__visit_name__ = "OBJECT"
|
|
65
|
+
|
|
66
|
+
def __init__(self, **items_types: Union[TypeEngine, Tuple[TypeEngine, bool]]):
|
|
67
|
+
for key, value in items_types.items():
|
|
68
|
+
if not isinstance(value, tuple):
|
|
69
|
+
items_types[key] = (value, False)
|
|
70
|
+
|
|
71
|
+
self.items_types = items_types
|
|
72
|
+
self.is_semi_structured = len(items_types) == 0
|
|
73
|
+
super().__init__()
|
|
74
|
+
|
|
75
|
+
def __repr__(self):
|
|
76
|
+
quote_char = "'"
|
|
77
|
+
return "OBJECT(%s)" % ", ".join(
|
|
78
|
+
[
|
|
79
|
+
f"{repr(key).strip(quote_char)}={repr(value)}"
|
|
80
|
+
for key, value in self.items_types.items()
|
|
81
|
+
]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ARRAY(StructuredType):
|
|
86
|
+
__visit_name__ = "SNOWFLAKE_ARRAY"
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
value_type: Optional[sqltypes.TypeEngine] = None,
|
|
91
|
+
not_null: bool = False,
|
|
92
|
+
):
|
|
93
|
+
self.value_type = value_type
|
|
94
|
+
self.not_null = not_null
|
|
95
|
+
super().__init__(is_semi_structured=value_type is None)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TIMESTAMP_TZ(SnowflakeType):
|
|
99
|
+
__visit_name__ = "TIMESTAMP_TZ"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TIMESTAMP_LTZ(SnowflakeType):
|
|
103
|
+
__visit_name__ = "TIMESTAMP_LTZ"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TIMESTAMP_NTZ(SnowflakeType):
|
|
107
|
+
__visit_name__ = "TIMESTAMP_NTZ"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class GEOGRAPHY(SnowflakeType):
|
|
111
|
+
__visit_name__ = "GEOGRAPHY"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class GEOMETRY(SnowflakeType):
|
|
115
|
+
__visit_name__ = "GEOMETRY"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class _CUSTOM_Date(SnowflakeType, sqltypes.Date):
|
|
119
|
+
def literal_processor(self, dialect):
|
|
120
|
+
def process(value):
|
|
121
|
+
if value is not None:
|
|
122
|
+
return f"'{value.isoformat()}'"
|
|
123
|
+
|
|
124
|
+
return process
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class _CUSTOM_DateTime(SnowflakeType, sqltypes.DateTime):
|
|
128
|
+
def literal_processor(self, dialect):
|
|
129
|
+
def process(value):
|
|
130
|
+
if value is not None:
|
|
131
|
+
datetime_str = value.isoformat(" ", timespec="microseconds")
|
|
132
|
+
return f"'{datetime_str}'"
|
|
133
|
+
|
|
134
|
+
return process
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class _CUSTOM_Time(SnowflakeType, sqltypes.Time):
|
|
138
|
+
def literal_processor(self, dialect):
|
|
139
|
+
def process(value):
|
|
140
|
+
if value is not None:
|
|
141
|
+
time_str = value.isoformat(timespec="microseconds")
|
|
142
|
+
return f"'{time_str}'"
|
|
143
|
+
|
|
144
|
+
return process
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _CUSTOM_Float(SnowflakeType, sqltypes.Float):
|
|
148
|
+
def bind_processor(self, dialect):
|
|
149
|
+
return _process_float
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class _CUSTOM_DECIMAL(SnowflakeType, sqltypes.DECIMAL):
|
|
153
|
+
@util.memoized_property
|
|
154
|
+
def _type_affinity(self):
|
|
155
|
+
return sqltypes.INTEGER if self.scale == 0 else sqltypes.DECIMAL
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.exc import ArgumentError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NoPrimaryKeyError(ArgumentError):
|
|
10
|
+
def __init__(self, target: str):
|
|
11
|
+
super().__init__(f"Table {target} required primary key.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UnsupportedPrimaryKeysAndForeignKeysError(ArgumentError):
|
|
15
|
+
def __init__(self, target: str):
|
|
16
|
+
super().__init__(f"Primary key and foreign keys are not supported in {target}.")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RequiredParametersNotProvidedError(ArgumentError):
|
|
20
|
+
def __init__(self, target: str, parameters: List[str]):
|
|
21
|
+
super().__init__(
|
|
22
|
+
f"{target} requires the following parameters: %s." % ", ".join(parameters)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UnexpectedTableOptionKeyError(ArgumentError):
|
|
27
|
+
def __init__(self, expected: str, actual: str):
|
|
28
|
+
super().__init__(f"Expected table option {expected} but got {actual}.")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OptionKeyNotProvidedError(ArgumentError):
|
|
32
|
+
def __init__(self, target: str):
|
|
33
|
+
super().__init__(
|
|
34
|
+
f"Expected option key in {target} option but got NoneType instead."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class UnexpectedOptionParameterTypeError(ArgumentError):
|
|
39
|
+
def __init__(self, parameter_name: str, target: str, types: List[str]):
|
|
40
|
+
super().__init__(
|
|
41
|
+
f"Parameter {parameter_name} of {target} requires to be one"
|
|
42
|
+
f" of following types: {', '.join(types)}."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CustomOptionsAreOnlySupportedOnSnowflakeTables(ArgumentError):
|
|
47
|
+
def __init__(self):
|
|
48
|
+
super().__init__(
|
|
49
|
+
"Identifier, Literal, TargetLag and other custom options are only supported on Snowflake tables."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class UnexpectedOptionTypeError(ArgumentError):
|
|
54
|
+
def __init__(self, options: List[str]):
|
|
55
|
+
super().__init__(
|
|
56
|
+
f"The following options are either unsupported or should be defined using a Snowflake table: {', '.join(options)}."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class InvalidTableParameterTypeError(ArgumentError):
|
|
61
|
+
def __init__(self, name: str, input_type: str, expected_types: List[str]):
|
|
62
|
+
expected_types_str = "', '".join(expected_types)
|
|
63
|
+
super().__init__(
|
|
64
|
+
f"Invalid parameter type '{input_type}' provided for '{name}'. "
|
|
65
|
+
f"Expected one of the following types: '{expected_types_str}'.\n"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MultipleErrors(ArgumentError):
|
|
70
|
+
def __init__(self, errors):
|
|
71
|
+
self.errors = errors
|
|
72
|
+
|
|
73
|
+
def __str__(self):
|
|
74
|
+
return "".join(str(e) for e in self.errors)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class StructuredTypeNotSupportedInTableColumnsError(ArgumentError):
|
|
78
|
+
def __init__(self, table_type: str, table_name: str, column_name: str):
|
|
79
|
+
super().__init__(
|
|
80
|
+
f"Column '{column_name}' is of a structured type, which is only supported on Iceberg tables. "
|
|
81
|
+
f"The table '{table_name}' is of type '{table_type}', not Iceberg."
|
|
82
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.sql import functions as sqlfunc
|
|
7
|
+
|
|
8
|
+
FLATTEN_WARNING = "For backward compatibility params are not rendered."
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class flatten(sqlfunc.GenericFunction):
|
|
12
|
+
name = "flatten"
|
|
13
|
+
|
|
14
|
+
def __init__(self, *args, **kwargs):
|
|
15
|
+
warnings.warn(FLATTEN_WARNING, DeprecationWarning, stacklevel=2)
|
|
16
|
+
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import sqlalchemy.types as sqltypes
|
|
6
|
+
from sqlalchemy.sql.type_api import TypeEngine
|
|
7
|
+
from sqlalchemy.types import (
|
|
8
|
+
BIGINT,
|
|
9
|
+
BINARY,
|
|
10
|
+
BOOLEAN,
|
|
11
|
+
CHAR,
|
|
12
|
+
DATE,
|
|
13
|
+
DATETIME,
|
|
14
|
+
DECIMAL,
|
|
15
|
+
FLOAT,
|
|
16
|
+
INTEGER,
|
|
17
|
+
REAL,
|
|
18
|
+
SMALLINT,
|
|
19
|
+
TIME,
|
|
20
|
+
TIMESTAMP,
|
|
21
|
+
VARCHAR,
|
|
22
|
+
NullType,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from ..custom_types import (
|
|
26
|
+
_CUSTOM_DECIMAL,
|
|
27
|
+
ARRAY,
|
|
28
|
+
DOUBLE,
|
|
29
|
+
GEOGRAPHY,
|
|
30
|
+
GEOMETRY,
|
|
31
|
+
MAP,
|
|
32
|
+
OBJECT,
|
|
33
|
+
TIMESTAMP_LTZ,
|
|
34
|
+
TIMESTAMP_NTZ,
|
|
35
|
+
TIMESTAMP_TZ,
|
|
36
|
+
VARIANT,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
ischema_names = {
|
|
40
|
+
"BIGINT": BIGINT,
|
|
41
|
+
"BINARY": BINARY,
|
|
42
|
+
# 'BIT': BIT,
|
|
43
|
+
"BOOLEAN": BOOLEAN,
|
|
44
|
+
"CHAR": CHAR,
|
|
45
|
+
"CHARACTER": CHAR,
|
|
46
|
+
"DATE": DATE,
|
|
47
|
+
"DATETIME": DATETIME,
|
|
48
|
+
"DEC": DECIMAL,
|
|
49
|
+
"DECIMAL": DECIMAL,
|
|
50
|
+
"DOUBLE": DOUBLE,
|
|
51
|
+
"FIXED": DECIMAL,
|
|
52
|
+
"FLOAT": FLOAT, # Snowflake FLOAT datatype doesn't have parameters
|
|
53
|
+
"INT": INTEGER,
|
|
54
|
+
"INTEGER": INTEGER,
|
|
55
|
+
"NUMBER": _CUSTOM_DECIMAL,
|
|
56
|
+
"REAL": REAL,
|
|
57
|
+
"BYTEINT": SMALLINT,
|
|
58
|
+
"SMALLINT": SMALLINT,
|
|
59
|
+
"STRING": VARCHAR,
|
|
60
|
+
"TEXT": VARCHAR,
|
|
61
|
+
"TIME": TIME,
|
|
62
|
+
"TIMESTAMP": TIMESTAMP,
|
|
63
|
+
"TIMESTAMP_TZ": TIMESTAMP_TZ,
|
|
64
|
+
"TIMESTAMP_LTZ": TIMESTAMP_LTZ,
|
|
65
|
+
"TIMESTAMP_NTZ": TIMESTAMP_NTZ,
|
|
66
|
+
"TINYINT": SMALLINT,
|
|
67
|
+
"VARBINARY": BINARY,
|
|
68
|
+
"VARCHAR": VARCHAR,
|
|
69
|
+
"VARIANT": VARIANT,
|
|
70
|
+
"MAP": MAP,
|
|
71
|
+
"OBJECT": OBJECT,
|
|
72
|
+
"ARRAY": ARRAY,
|
|
73
|
+
"GEOGRAPHY": GEOGRAPHY,
|
|
74
|
+
"GEOMETRY": GEOMETRY,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
NOT_NULL_STR = "NOT NULL"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def tokenize_parameters(text: str, character_for_strip=",") -> list:
|
|
81
|
+
"""
|
|
82
|
+
Extracts parameters from a comma-separated string, handling parentheses.
|
|
83
|
+
|
|
84
|
+
:param text: A string with comma-separated parameters, which may include parentheses.
|
|
85
|
+
|
|
86
|
+
:param character_for_strip: A character to strip the text.
|
|
87
|
+
|
|
88
|
+
:return: A list of parameters as strings.
|
|
89
|
+
|
|
90
|
+
:example:
|
|
91
|
+
For input `"a, (b, c), d"`, the output is `['a', '(b, c)', 'd']`.
|
|
92
|
+
"""
|
|
93
|
+
output_parameters = []
|
|
94
|
+
parameter = ""
|
|
95
|
+
open_parenthesis = 0
|
|
96
|
+
for c in text:
|
|
97
|
+
|
|
98
|
+
if c == "(":
|
|
99
|
+
open_parenthesis += 1
|
|
100
|
+
elif c == ")":
|
|
101
|
+
open_parenthesis -= 1
|
|
102
|
+
|
|
103
|
+
if open_parenthesis > 0 or c != character_for_strip:
|
|
104
|
+
parameter += c
|
|
105
|
+
elif c == character_for_strip:
|
|
106
|
+
output_parameters.append(parameter.strip(" "))
|
|
107
|
+
parameter = ""
|
|
108
|
+
if parameter != "":
|
|
109
|
+
output_parameters.append(parameter.strip(" "))
|
|
110
|
+
return output_parameters
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def parse_index_columns(columns: str) -> List[str]:
|
|
114
|
+
"""
|
|
115
|
+
Parses a string with a list of columns for an index.
|
|
116
|
+
|
|
117
|
+
:param columns: A string with a list of columns for an index, which may include parentheses.
|
|
118
|
+
:param compiler: A SQLAlchemy compiler.
|
|
119
|
+
|
|
120
|
+
:return: A list of columns as strings.
|
|
121
|
+
|
|
122
|
+
:example:
|
|
123
|
+
For input `"[A, B, C]"`, the output is `['A', 'B', 'C']`.
|
|
124
|
+
"""
|
|
125
|
+
return [column.strip() for column in columns.strip("[]").split(",")]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def parse_type(type_text: str) -> TypeEngine:
|
|
129
|
+
"""
|
|
130
|
+
Parses a type definition string and returns the corresponding SQLAlchemy type.
|
|
131
|
+
|
|
132
|
+
The function handles types with or without parameters, such as `VARCHAR(255)` or `INTEGER`.
|
|
133
|
+
|
|
134
|
+
:param type_text: A string representing a SQLAlchemy type, which may include parameters
|
|
135
|
+
in parentheses (e.g., "VARCHAR(255)" or "DECIMAL(10, 2)").
|
|
136
|
+
:return: An instance of the corresponding SQLAlchemy type class (e.g., `String`, `Integer`),
|
|
137
|
+
or `NullType` if the type is not recognized.
|
|
138
|
+
|
|
139
|
+
:example:
|
|
140
|
+
parse_type("VARCHAR(255)")
|
|
141
|
+
String(length=255)
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
index = type_text.find("(")
|
|
145
|
+
type_name = type_text[:index] if index != -1 else type_text
|
|
146
|
+
|
|
147
|
+
parameters = (
|
|
148
|
+
tokenize_parameters(type_text[index + 1 : -1]) if type_name != type_text else []
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
col_type_class = ischema_names.get(type_name, None)
|
|
152
|
+
col_type_kw = {}
|
|
153
|
+
|
|
154
|
+
if col_type_class is None:
|
|
155
|
+
col_type_class = NullType
|
|
156
|
+
else:
|
|
157
|
+
if issubclass(col_type_class, sqltypes.Numeric):
|
|
158
|
+
col_type_kw = __parse_numeric_type_parameters(parameters)
|
|
159
|
+
elif issubclass(col_type_class, (sqltypes.String, sqltypes.BINARY)):
|
|
160
|
+
col_type_kw = __parse_type_with_length_parameters(parameters)
|
|
161
|
+
elif issubclass(col_type_class, MAP):
|
|
162
|
+
col_type_kw = __parse_map_type_parameters(parameters)
|
|
163
|
+
elif issubclass(col_type_class, OBJECT):
|
|
164
|
+
col_type_kw = __parse_object_type_parameters(parameters)
|
|
165
|
+
elif issubclass(col_type_class, ARRAY):
|
|
166
|
+
col_type_kw = __parse_nullable_parameter(parameters)
|
|
167
|
+
if col_type_kw is None:
|
|
168
|
+
col_type_class = NullType
|
|
169
|
+
col_type_kw = {}
|
|
170
|
+
|
|
171
|
+
return col_type_class(**col_type_kw)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def __parse_object_type_parameters(parameters):
|
|
175
|
+
object_rows = {}
|
|
176
|
+
not_null_parts = NOT_NULL_STR.split(" ")
|
|
177
|
+
for parameter in parameters:
|
|
178
|
+
parameter_parts = tokenize_parameters(parameter, " ")
|
|
179
|
+
if len(parameter_parts) >= 2:
|
|
180
|
+
key = parameter_parts[0]
|
|
181
|
+
value_type = parse_type(parameter_parts[1])
|
|
182
|
+
if isinstance(value_type, NullType):
|
|
183
|
+
return None
|
|
184
|
+
not_null = (
|
|
185
|
+
len(parameter_parts) == 4
|
|
186
|
+
and parameter_parts[2] == not_null_parts[0]
|
|
187
|
+
and parameter_parts[3] == not_null_parts[1]
|
|
188
|
+
)
|
|
189
|
+
object_rows[key] = (value_type, not_null)
|
|
190
|
+
return object_rows
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def __parse_nullable_parameter(parameters):
|
|
194
|
+
if len(parameters) < 1:
|
|
195
|
+
return {}
|
|
196
|
+
elif len(parameters) > 1:
|
|
197
|
+
return None
|
|
198
|
+
parameter_str = parameters[0]
|
|
199
|
+
is_not_null = False
|
|
200
|
+
if (
|
|
201
|
+
len(parameter_str) >= len(NOT_NULL_STR)
|
|
202
|
+
and parameter_str[-len(NOT_NULL_STR) :] == NOT_NULL_STR
|
|
203
|
+
):
|
|
204
|
+
is_not_null = True
|
|
205
|
+
parameter_str = parameter_str[: -len(NOT_NULL_STR) - 1]
|
|
206
|
+
|
|
207
|
+
value_type: TypeEngine = parse_type(parameter_str)
|
|
208
|
+
if isinstance(value_type, NullType):
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
"value_type": value_type,
|
|
213
|
+
"not_null": is_not_null,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def __parse_map_type_parameters(parameters):
|
|
218
|
+
if len(parameters) != 2:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
key_type_str = parameters[0]
|
|
222
|
+
value_type_str = parameters[1]
|
|
223
|
+
key_type: TypeEngine = parse_type(key_type_str)
|
|
224
|
+
value_type = __parse_nullable_parameter([value_type_str])
|
|
225
|
+
if isinstance(value_type, NullType) or isinstance(key_type, NullType):
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
return {"key_type": key_type, **value_type}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def __parse_type_with_length_parameters(parameters):
|
|
232
|
+
return (
|
|
233
|
+
{"length": int(parameters[0])}
|
|
234
|
+
if len(parameters) == 1 and str.isdigit(parameters[0])
|
|
235
|
+
else {}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def __parse_numeric_type_parameters(parameters):
|
|
240
|
+
result = {}
|
|
241
|
+
if len(parameters) >= 1 and str.isdigit(parameters[0]):
|
|
242
|
+
result["precision"] = int(parameters[0])
|
|
243
|
+
if len(parameters) == 2 and str.isdigit(parameters[1]):
|
|
244
|
+
result["scale"] = int(parameters[1])
|
|
245
|
+
return result
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
from sqlalchemy.testing.provision import set_default_schema_on_connection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# This is only for test purpose required by Requirement "default_schema_name_switch"
|
|
8
|
+
@set_default_schema_on_connection.for_db("snowflake")
|
|
9
|
+
def _snowflake_set_default_schema_on_connection(cfg, dbapi_connection, schema_name):
|
|
10
|
+
cursor = dbapi_connection.cursor()
|
|
11
|
+
cursor.execute(f"USE SCHEMA {dbapi_connection.database}.{schema_name};")
|
|
12
|
+
cursor.close()
|