meerschaum 2.6.17__py3-none-any.whl → 2.7.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- meerschaum/actions/delete.py +65 -69
- meerschaum/actions/install.py +1 -2
- meerschaum/api/routes/_pipes.py +7 -8
- meerschaum/config/_default.py +1 -1
- meerschaum/config/_paths.py +2 -1
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/api/_pipes.py +18 -21
- meerschaum/connectors/sql/_instance.py +11 -12
- meerschaum/connectors/sql/_pipes.py +122 -78
- meerschaum/connectors/sql/_sql.py +43 -8
- meerschaum/connectors/valkey/_pipes.py +12 -1
- meerschaum/core/Pipe/__init__.py +23 -13
- meerschaum/core/Pipe/_attributes.py +25 -1
- meerschaum/core/Pipe/_dtypes.py +23 -16
- meerschaum/core/Pipe/_sync.py +59 -31
- meerschaum/core/Pipe/_verify.py +8 -7
- meerschaum/jobs/_Job.py +2 -0
- meerschaum/plugins/_Plugin.py +11 -14
- meerschaum/utils/daemon/Daemon.py +20 -13
- meerschaum/utils/dataframe.py +178 -16
- meerschaum/utils/dtypes/__init__.py +149 -14
- meerschaum/utils/dtypes/sql.py +41 -7
- meerschaum/utils/misc.py +8 -8
- meerschaum/utils/sql.py +174 -64
- meerschaum/utils/venv/_Venv.py +4 -4
- meerschaum/utils/venv/__init__.py +53 -20
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/METADATA +1 -1
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/RECORD +34 -34
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/WHEEL +0 -0
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/zip-safe +0 -0
meerschaum/utils/misc.py
CHANGED
@@ -177,14 +177,14 @@ def string_to_dict(
|
|
177
177
|
keys = _keys[:-1]
|
178
178
|
try:
|
179
179
|
val = ast.literal_eval(_keys[-1])
|
180
|
-
except Exception
|
180
|
+
except Exception:
|
181
181
|
val = str(_keys[-1])
|
182
182
|
|
183
183
|
c = params_dict
|
184
184
|
for _k in keys[:-1]:
|
185
185
|
try:
|
186
186
|
k = ast.literal_eval(_k)
|
187
|
-
except Exception
|
187
|
+
except Exception:
|
188
188
|
k = str(_k)
|
189
189
|
if k not in c:
|
190
190
|
c[k] = {}
|
@@ -196,12 +196,12 @@ def string_to_dict(
|
|
196
196
|
|
197
197
|
|
198
198
|
def parse_config_substitution(
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
199
|
+
value: str,
|
200
|
+
leading_key: str = 'MRSM',
|
201
|
+
begin_key: str = '{',
|
202
|
+
end_key: str = '}',
|
203
|
+
delimeter: str = ':'
|
204
|
+
) -> List[Any]:
|
205
205
|
"""
|
206
206
|
Parse Meerschaum substitution syntax
|
207
207
|
E.g. MRSM{value1:value2} => ['value1', 'value2']
|
meerschaum/utils/sql.py
CHANGED
@@ -7,6 +7,7 @@ Flavor-specific SQL tools.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
10
11
|
from datetime import datetime, timezone, timedelta
|
11
12
|
import meerschaum as mrsm
|
12
13
|
from meerschaum.utils.typing import Optional, Dict, Any, Union, List, Iterable, Tuple
|
@@ -50,10 +51,12 @@ update_queries = {
|
|
50
51
|
{sets_subquery_none}
|
51
52
|
FROM {target_table_name} AS t
|
52
53
|
INNER JOIN (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) AS p
|
53
|
-
ON
|
54
|
+
ON
|
55
|
+
{and_subquery_t}
|
54
56
|
WHERE
|
55
57
|
{and_subquery_f}
|
56
|
-
AND
|
58
|
+
AND
|
59
|
+
{date_bounds_subquery}
|
57
60
|
""",
|
58
61
|
'timescaledb-upsert': """
|
59
62
|
INSERT INTO {target_table_name} ({patch_cols_str})
|
@@ -82,9 +85,11 @@ update_queries = {
|
|
82
85
|
'mysql': """
|
83
86
|
UPDATE {target_table_name} AS f
|
84
87
|
JOIN (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) AS p
|
85
|
-
ON
|
88
|
+
ON
|
89
|
+
{and_subquery_f}
|
86
90
|
{sets_subquery_f}
|
87
|
-
WHERE
|
91
|
+
WHERE
|
92
|
+
{date_bounds_subquery}
|
88
93
|
""",
|
89
94
|
'mysql-upsert': """
|
90
95
|
INSERT {ignore}INTO {target_table_name} ({patch_cols_str})
|
@@ -96,9 +101,11 @@ update_queries = {
|
|
96
101
|
'mariadb': """
|
97
102
|
UPDATE {target_table_name} AS f
|
98
103
|
JOIN (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) AS p
|
99
|
-
ON
|
104
|
+
ON
|
105
|
+
{and_subquery_f}
|
100
106
|
{sets_subquery_f}
|
101
|
-
WHERE
|
107
|
+
WHERE
|
108
|
+
{date_bounds_subquery}
|
102
109
|
""",
|
103
110
|
'mariadb-upsert': """
|
104
111
|
INSERT {ignore}INTO {target_table_name} ({patch_cols_str})
|
@@ -108,34 +115,56 @@ update_queries = {
|
|
108
115
|
{cols_equal_values}
|
109
116
|
""",
|
110
117
|
'mssql': """
|
118
|
+
{with_temp_date_bounds}
|
111
119
|
MERGE {target_table_name} f
|
112
|
-
USING (SELECT
|
113
|
-
ON
|
114
|
-
|
120
|
+
USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
|
121
|
+
ON
|
122
|
+
{and_subquery_f}
|
123
|
+
AND
|
124
|
+
{date_bounds_subquery}
|
115
125
|
WHEN MATCHED THEN
|
116
126
|
UPDATE
|
117
127
|
{sets_subquery_none};
|
118
128
|
""",
|
119
|
-
'mssql-upsert':
|
129
|
+
'mssql-upsert': [
|
130
|
+
"{identity_insert_on}",
|
131
|
+
"""
|
132
|
+
{with_temp_date_bounds}
|
120
133
|
MERGE {target_table_name} f
|
121
|
-
USING (SELECT
|
122
|
-
ON
|
123
|
-
|
124
|
-
|
134
|
+
USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
|
135
|
+
ON
|
136
|
+
{and_subquery_f}
|
137
|
+
AND
|
138
|
+
{date_bounds_subquery}{when_matched_update_sets_subquery_none}
|
125
139
|
WHEN NOT MATCHED THEN
|
126
140
|
INSERT ({patch_cols_str})
|
127
141
|
VALUES ({patch_cols_prefixed_str});
|
128
|
-
|
142
|
+
""",
|
143
|
+
"{identity_insert_off}",
|
144
|
+
],
|
129
145
|
'oracle': """
|
130
146
|
MERGE INTO {target_table_name} f
|
131
|
-
USING (SELECT
|
147
|
+
USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
|
132
148
|
ON (
|
133
149
|
{and_subquery_f}
|
134
|
-
AND
|
150
|
+
AND
|
151
|
+
{date_bounds_subquery}
|
135
152
|
)
|
136
|
-
|
137
|
-
|
138
|
-
|
153
|
+
WHEN MATCHED THEN
|
154
|
+
UPDATE
|
155
|
+
{sets_subquery_none}
|
156
|
+
""",
|
157
|
+
'oracle-upsert': """
|
158
|
+
MERGE INTO {target_table_name} f
|
159
|
+
USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
|
160
|
+
ON (
|
161
|
+
{and_subquery_f}
|
162
|
+
AND
|
163
|
+
{date_bounds_subquery}
|
164
|
+
){when_matched_update_sets_subquery_none}
|
165
|
+
WHEN NOT MATCHED THEN
|
166
|
+
INSERT ({patch_cols_str})
|
167
|
+
VALUES ({patch_cols_prefixed_str})
|
139
168
|
""",
|
140
169
|
'sqlite-upsert': """
|
141
170
|
INSERT INTO {target_table_name} ({patch_cols_str})
|
@@ -323,7 +352,11 @@ columns_indices_queries = {
|
|
323
352
|
CASE
|
324
353
|
WHEN kc.type = 'PK' THEN 'PRIMARY KEY'
|
325
354
|
ELSE 'INDEX'
|
326
|
-
END AS [index_type]
|
355
|
+
END AS [index_type],
|
356
|
+
CASE
|
357
|
+
WHEN i.type = 1 THEN CAST(1 AS BIT)
|
358
|
+
ELSE CAST(0 AS BIT)
|
359
|
+
END AS [clustered]
|
327
360
|
FROM
|
328
361
|
sys.schemas s
|
329
362
|
INNER JOIN sys.tables t
|
@@ -489,7 +522,8 @@ def dateadd_str(
|
|
489
522
|
flavor: str = 'postgresql',
|
490
523
|
datepart: str = 'day',
|
491
524
|
number: Union[int, float] = 0,
|
492
|
-
begin: Union[str, datetime, int] = 'now'
|
525
|
+
begin: Union[str, datetime, int] = 'now',
|
526
|
+
db_type: Optional[str] = None,
|
493
527
|
) -> str:
|
494
528
|
"""
|
495
529
|
Generate a `DATEADD` clause depending on database flavor.
|
@@ -528,6 +562,10 @@ def dateadd_str(
|
|
528
562
|
begin: Union[str, datetime], default `'now'`
|
529
563
|
Base datetime to which to add dateparts.
|
530
564
|
|
565
|
+
db_type: Optional[str], default None
|
566
|
+
If provided, cast the datetime string as the type.
|
567
|
+
Otherwise, infer this from the input datetime value.
|
568
|
+
|
531
569
|
Returns
|
532
570
|
-------
|
533
571
|
The appropriate `DATEADD` string for the corresponding database flavor.
|
@@ -539,7 +577,7 @@ def dateadd_str(
|
|
539
577
|
... begin = datetime(2022, 1, 1, 0, 0),
|
540
578
|
... number = 1,
|
541
579
|
... )
|
542
|
-
"DATEADD(day, 1, CAST('2022-01-01 00:00:00' AS
|
580
|
+
"DATEADD(day, 1, CAST('2022-01-01 00:00:00' AS DATETIME2))"
|
543
581
|
>>> dateadd_str(
|
544
582
|
... flavor = 'postgresql',
|
545
583
|
... begin = datetime(2022, 1, 1, 0, 0),
|
@@ -582,7 +620,7 @@ def dateadd_str(
|
|
582
620
|
)
|
583
621
|
|
584
622
|
dt_is_utc = begin_time.tzinfo is not None if begin_time is not None else '+' in str(begin)
|
585
|
-
db_type = get_db_type_from_pd_type(
|
623
|
+
db_type = db_type or get_db_type_from_pd_type(
|
586
624
|
('datetime64[ns, UTC]' if dt_is_utc else 'datetime64[ns]'),
|
587
625
|
flavor=flavor,
|
588
626
|
)
|
@@ -707,7 +745,7 @@ def get_distinct_col_count(
|
|
707
745
|
result = connector.value(_meta_query, debug=debug)
|
708
746
|
try:
|
709
747
|
return int(result)
|
710
|
-
except Exception
|
748
|
+
except Exception:
|
711
749
|
return None
|
712
750
|
|
713
751
|
|
@@ -717,12 +755,15 @@ def sql_item_name(item: str, flavor: str, schema: Optional[str] = None) -> str:
|
|
717
755
|
|
718
756
|
Parameters
|
719
757
|
----------
|
720
|
-
item: str
|
758
|
+
item: str
|
721
759
|
The database item (table, view, etc.) in need of quotes.
|
722
760
|
|
723
|
-
flavor: str
|
761
|
+
flavor: str
|
724
762
|
The database flavor (`'postgresql'`, `'mssql'`, `'sqllite'`, etc.).
|
725
763
|
|
764
|
+
schema: Optional[str], default None
|
765
|
+
If provided, prefix the table name with the schema.
|
766
|
+
|
726
767
|
Returns
|
727
768
|
-------
|
728
769
|
A `str` which contains the input `item` wrapped in the corresponding escape characters.
|
@@ -754,6 +795,8 @@ def sql_item_name(item: str, flavor: str, schema: Optional[str] = None) -> str:
|
|
754
795
|
### NOTE: SQLite does not support schemas.
|
755
796
|
if flavor == 'sqlite':
|
756
797
|
schema = None
|
798
|
+
elif flavor == 'mssql' and str(item).startswith('#'):
|
799
|
+
schema = None
|
757
800
|
|
758
801
|
schema_prefix = (
|
759
802
|
(wrappers[0] + schema + wrappers[1] + '.')
|
@@ -1056,7 +1099,7 @@ def get_sqlalchemy_table(
|
|
1056
1099
|
connector.metadata,
|
1057
1100
|
**table_kwargs
|
1058
1101
|
)
|
1059
|
-
except sqlalchemy.exc.NoSuchTableError
|
1102
|
+
except sqlalchemy.exc.NoSuchTableError:
|
1060
1103
|
warn(f"Table '{truncated_table_name}' does not exist in '{connector}'.")
|
1061
1104
|
return None
|
1062
1105
|
return tables[truncated_table_name]
|
@@ -1109,6 +1152,7 @@ def get_table_cols_types(
|
|
1109
1152
|
-------
|
1110
1153
|
A dictionary mapping column names to data types.
|
1111
1154
|
"""
|
1155
|
+
import textwrap
|
1112
1156
|
from meerschaum.connectors import SQLConnector
|
1113
1157
|
sqlalchemy = mrsm.attempt_import('sqlalchemy')
|
1114
1158
|
flavor = flavor or getattr(connectable, 'flavor', None)
|
@@ -1134,7 +1178,7 @@ def get_table_cols_types(
|
|
1134
1178
|
)
|
1135
1179
|
|
1136
1180
|
cols_types_query = sqlalchemy.text(
|
1137
|
-
columns_types_queries.get(
|
1181
|
+
textwrap.dedent(columns_types_queries.get(
|
1138
1182
|
flavor,
|
1139
1183
|
columns_types_queries['default']
|
1140
1184
|
).format(
|
@@ -1145,7 +1189,7 @@ def get_table_cols_types(
|
|
1145
1189
|
table_upper=table_upper,
|
1146
1190
|
table_upper_trunc=table_upper_trunc,
|
1147
1191
|
db_prefix=db_prefix,
|
1148
|
-
)
|
1192
|
+
)).lstrip().rstrip()
|
1149
1193
|
)
|
1150
1194
|
|
1151
1195
|
cols = ['database', 'schema', 'table', 'column', 'type']
|
@@ -1259,6 +1303,7 @@ def get_table_cols_indices(
|
|
1259
1303
|
-------
|
1260
1304
|
A dictionary mapping column names to a list of indices.
|
1261
1305
|
"""
|
1306
|
+
import textwrap
|
1262
1307
|
from collections import defaultdict
|
1263
1308
|
from meerschaum.connectors import SQLConnector
|
1264
1309
|
sqlalchemy = mrsm.attempt_import('sqlalchemy')
|
@@ -1285,7 +1330,7 @@ def get_table_cols_indices(
|
|
1285
1330
|
)
|
1286
1331
|
|
1287
1332
|
cols_indices_query = sqlalchemy.text(
|
1288
|
-
columns_indices_queries.get(
|
1333
|
+
textwrap.dedent(columns_indices_queries.get(
|
1289
1334
|
flavor,
|
1290
1335
|
columns_indices_queries['default']
|
1291
1336
|
).format(
|
@@ -1297,10 +1342,12 @@ def get_table_cols_indices(
|
|
1297
1342
|
table_upper_trunc=table_upper_trunc,
|
1298
1343
|
db_prefix=db_prefix,
|
1299
1344
|
schema=schema,
|
1300
|
-
)
|
1345
|
+
)).lstrip().rstrip()
|
1301
1346
|
)
|
1302
1347
|
|
1303
1348
|
cols = ['database', 'schema', 'table', 'column', 'index', 'index_type']
|
1349
|
+
if flavor == 'mssql':
|
1350
|
+
cols.append('clustered')
|
1304
1351
|
result_cols_ix = dict(enumerate(cols))
|
1305
1352
|
|
1306
1353
|
debug_kwargs = {'debug': debug} if isinstance(connectable, SQLConnector) else {}
|
@@ -1341,7 +1388,6 @@ def get_table_cols_indices(
|
|
1341
1388
|
)
|
1342
1389
|
)
|
1343
1390
|
]
|
1344
|
-
|
1345
1391
|
### NOTE: This may return incorrect columns if the schema is not explicitly stated.
|
1346
1392
|
if cols_types_docs and not cols_types_docs_filtered:
|
1347
1393
|
cols_types_docs_filtered = cols_types_docs
|
@@ -1357,12 +1403,13 @@ def get_table_cols_indices(
|
|
1357
1403
|
else doc['column']
|
1358
1404
|
)
|
1359
1405
|
)
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1406
|
+
index_doc = {
|
1407
|
+
'name': doc.get('index', None),
|
1408
|
+
'type': doc.get('index_type', None)
|
1409
|
+
}
|
1410
|
+
if flavor == 'mssql':
|
1411
|
+
index_doc['clustered'] = doc.get('clustered', None)
|
1412
|
+
cols_indices[col].append(index_doc)
|
1366
1413
|
|
1367
1414
|
return dict(cols_indices)
|
1368
1415
|
except Exception as e:
|
@@ -1383,6 +1430,7 @@ def get_update_queries(
|
|
1383
1430
|
datetime_col: Optional[str] = None,
|
1384
1431
|
schema: Optional[str] = None,
|
1385
1432
|
patch_schema: Optional[str] = None,
|
1433
|
+
identity_insert: bool = False,
|
1386
1434
|
debug: bool = False,
|
1387
1435
|
) -> List[str]:
|
1388
1436
|
"""
|
@@ -1420,6 +1468,10 @@ def get_update_queries(
|
|
1420
1468
|
If provided, use this schema when quoting the patch table.
|
1421
1469
|
Defaults to `schema`.
|
1422
1470
|
|
1471
|
+
identity_insert: bool, default False
|
1472
|
+
If `True`, include `SET IDENTITY_INSERT` queries before and after the update queries.
|
1473
|
+
Only applies for MSSQL upserts.
|
1474
|
+
|
1423
1475
|
debug: bool, default False
|
1424
1476
|
Verbosity toggle.
|
1425
1477
|
|
@@ -1427,9 +1479,11 @@ def get_update_queries(
|
|
1427
1479
|
-------
|
1428
1480
|
A list of query strings to perform the update operation.
|
1429
1481
|
"""
|
1482
|
+
import textwrap
|
1430
1483
|
from meerschaum.connectors import SQLConnector
|
1431
1484
|
from meerschaum.utils.debug import dprint
|
1432
|
-
from meerschaum.utils.dtypes
|
1485
|
+
from meerschaum.utils.dtypes import are_dtypes_equal
|
1486
|
+
from meerschaum.utils.dtypes.sql import DB_FLAVORS_CAST_DTYPES, get_pd_type_from_db_type
|
1433
1487
|
flavor = flavor or (connectable.flavor if isinstance(connectable, SQLConnector) else None)
|
1434
1488
|
if not flavor:
|
1435
1489
|
raise ValueError("Provide a flavor if using a SQLAlchemy session.")
|
@@ -1522,21 +1576,35 @@ def get_update_queries(
|
|
1522
1576
|
def sets_subquery(l_prefix: str, r_prefix: str):
|
1523
1577
|
if not value_cols:
|
1524
1578
|
return ''
|
1579
|
+
|
1580
|
+
cast_func_cols = {
|
1581
|
+
c_name: (
|
1582
|
+
('', '', '')
|
1583
|
+
if (
|
1584
|
+
flavor == 'oracle'
|
1585
|
+
and are_dtypes_equal(get_pd_type_from_db_type(c_type), 'bytes')
|
1586
|
+
)
|
1587
|
+
else (
|
1588
|
+
('CAST(', f" AS {c_type.replace('_', ' ')}", ')')
|
1589
|
+
if flavor != 'sqlite'
|
1590
|
+
else ('', '', '')
|
1591
|
+
)
|
1592
|
+
)
|
1593
|
+
for c_name, c_type in value_cols
|
1594
|
+
}
|
1525
1595
|
return 'SET ' + ',\n'.join([
|
1526
1596
|
(
|
1527
1597
|
l_prefix + sql_item_name(c_name, flavor, None)
|
1528
1598
|
+ ' = '
|
1529
|
-
+
|
1530
|
-
+ r_prefix
|
1531
|
-
+
|
1532
|
-
+
|
1533
|
-
+ (c_type.replace('_', ' ') if flavor != 'sqlite' else '')
|
1534
|
-
+ (')' if flavor != 'sqlite' else '')
|
1599
|
+
+ cast_func_cols[c_name][0]
|
1600
|
+
+ r_prefix + sql_item_name(c_name, flavor, None)
|
1601
|
+
+ cast_func_cols[c_name][1]
|
1602
|
+
+ cast_func_cols[c_name][2]
|
1535
1603
|
) for c_name, c_type in value_cols
|
1536
1604
|
])
|
1537
1605
|
|
1538
1606
|
def and_subquery(l_prefix: str, r_prefix: str):
|
1539
|
-
return '\
|
1607
|
+
return '\n AND\n '.join([
|
1540
1608
|
(
|
1541
1609
|
"COALESCE("
|
1542
1610
|
+ l_prefix
|
@@ -1544,7 +1612,7 @@ def get_update_queries(
|
|
1544
1612
|
+ ", "
|
1545
1613
|
+ get_null_replacement(c_type, flavor)
|
1546
1614
|
+ ")"
|
1547
|
-
+ '
|
1615
|
+
+ '\n =\n '
|
1548
1616
|
+ "COALESCE("
|
1549
1617
|
+ r_prefix
|
1550
1618
|
+ sql_item_name(c_name, flavor, None)
|
@@ -1554,22 +1622,39 @@ def get_update_queries(
|
|
1554
1622
|
) for c_name, c_type in join_cols_types
|
1555
1623
|
])
|
1556
1624
|
|
1625
|
+
skip_query_val = ""
|
1557
1626
|
target_table_name = sql_item_name(target, flavor, schema)
|
1558
1627
|
patch_table_name = sql_item_name(patch, flavor, patch_schema)
|
1559
1628
|
dt_col_name = sql_item_name(datetime_col, flavor, None) if datetime_col else None
|
1629
|
+
date_bounds_table = patch_table_name if flavor != 'mssql' else '[date_bounds]'
|
1630
|
+
min_dt_col_name = f"MIN({dt_col_name})" if flavor != 'mssql' else '[Min_dt]'
|
1631
|
+
max_dt_col_name = f"MAX({dt_col_name})" if flavor != 'mssql' else '[Max_dt]'
|
1560
1632
|
date_bounds_subquery = (
|
1561
|
-
f"""
|
1562
|
-
|
1563
|
-
|
1564
|
-
"""
|
1633
|
+
f"""f.{dt_col_name} >= (SELECT {min_dt_col_name} FROM {date_bounds_table})
|
1634
|
+
AND
|
1635
|
+
f.{dt_col_name} <= (SELECT {max_dt_col_name} FROM {date_bounds_table})"""
|
1565
1636
|
if datetime_col
|
1566
1637
|
else "1 = 1"
|
1567
1638
|
)
|
1639
|
+
with_temp_date_bounds = f"""WITH [date_bounds] AS (
|
1640
|
+
SELECT MIN({dt_col_name}) AS {min_dt_col_name}, MAX({dt_col_name}) AS {max_dt_col_name}
|
1641
|
+
FROM {patch_table_name}
|
1642
|
+
)""" if datetime_col else ""
|
1643
|
+
identity_insert_on = (
|
1644
|
+
f"SET IDENTITY_INSERT {target_table_name} ON"
|
1645
|
+
if identity_insert
|
1646
|
+
else skip_query_val
|
1647
|
+
)
|
1648
|
+
identity_insert_off = (
|
1649
|
+
f"SET IDENTITY_INSERT {target_table_name} OFF"
|
1650
|
+
if identity_insert
|
1651
|
+
else skip_query_val
|
1652
|
+
)
|
1568
1653
|
|
1569
1654
|
### NOTE: MSSQL upserts must exclude the update portion if only upserting indices.
|
1570
1655
|
when_matched_update_sets_subquery_none = "" if not value_cols else (
|
1571
|
-
"WHEN MATCHED THEN"
|
1572
|
-
f"
|
1656
|
+
"\n WHEN MATCHED THEN\n"
|
1657
|
+
f" UPDATE {sets_subquery('', 'p.')}"
|
1573
1658
|
)
|
1574
1659
|
|
1575
1660
|
cols_equal_values = '\n,'.join(
|
@@ -1585,8 +1670,8 @@ def get_update_queries(
|
|
1585
1670
|
)
|
1586
1671
|
ignore = "IGNORE " if not value_cols else ""
|
1587
1672
|
|
1588
|
-
|
1589
|
-
base_query.format(
|
1673
|
+
formatted_queries = [
|
1674
|
+
textwrap.dedent(base_query.format(
|
1590
1675
|
sets_subquery_none=sets_subquery('', 'p.'),
|
1591
1676
|
sets_subquery_none_excluded=sets_subquery('', 'EXCLUDED.'),
|
1592
1677
|
sets_subquery_f=sets_subquery('f.', 'p.'),
|
@@ -1604,10 +1689,16 @@ def get_update_queries(
|
|
1604
1689
|
cols_equal_values=cols_equal_values,
|
1605
1690
|
on_duplicate_key_update=on_duplicate_key_update,
|
1606
1691
|
ignore=ignore,
|
1607
|
-
|
1692
|
+
with_temp_date_bounds=with_temp_date_bounds,
|
1693
|
+
identity_insert_on=identity_insert_on,
|
1694
|
+
identity_insert_off=identity_insert_off,
|
1695
|
+
)).lstrip().rstrip()
|
1608
1696
|
for base_query in base_queries
|
1609
1697
|
]
|
1610
1698
|
|
1699
|
+
### NOTE: Allow for skipping some queries.
|
1700
|
+
return [query for query in formatted_queries if query]
|
1701
|
+
|
1611
1702
|
|
1612
1703
|
def get_null_replacement(typ: str, flavor: str) -> str:
|
1613
1704
|
"""
|
@@ -1645,11 +1736,14 @@ def get_null_replacement(typ: str, flavor: str) -> str:
|
|
1645
1736
|
)
|
1646
1737
|
return f'CAST({val_to_cast} AS {bool_typ})'
|
1647
1738
|
if 'time' in typ.lower() or 'date' in typ.lower():
|
1648
|
-
|
1739
|
+
db_type = typ if typ.isupper() else None
|
1740
|
+
return dateadd_str(flavor=flavor, begin='1900-01-01', db_type=db_type)
|
1649
1741
|
if 'float' in typ.lower() or 'double' in typ.lower() or typ.lower() in ('decimal',):
|
1650
1742
|
return '-987654321.0'
|
1651
1743
|
if flavor == 'oracle' and typ.lower().split('(', maxsplit=1)[0] == 'char':
|
1652
1744
|
return "'-987654321'"
|
1745
|
+
if flavor == 'oracle' and typ.lower() in ('blob', 'bytes'):
|
1746
|
+
return '00'
|
1653
1747
|
if typ.lower() in ('uniqueidentifier', 'guid', 'uuid'):
|
1654
1748
|
magic_val = 'DEADBEEF-ABBA-BABE-CAFE-DECAFC0FFEE5'
|
1655
1749
|
if flavor == 'mssql':
|
@@ -1857,7 +1951,17 @@ def _get_create_table_query_from_dtypes(
|
|
1857
1951
|
|
1858
1952
|
table_name = sql_item_name(new_table, schema=schema, flavor=flavor)
|
1859
1953
|
primary_key_name = sql_item_name(primary_key, flavor) if primary_key else None
|
1954
|
+
primary_key_constraint_name = (
|
1955
|
+
sql_item_name(f'PK_{new_table}', flavor, None)
|
1956
|
+
if primary_key
|
1957
|
+
else None
|
1958
|
+
)
|
1860
1959
|
datetime_column_name = sql_item_name(datetime_column, flavor) if datetime_column else None
|
1960
|
+
primary_key_clustered = (
|
1961
|
+
"CLUSTERED"
|
1962
|
+
if not datetime_column or datetime_column == primary_key
|
1963
|
+
else "NONCLUSTERED"
|
1964
|
+
)
|
1861
1965
|
query = f"CREATE TABLE {table_name} ("
|
1862
1966
|
if primary_key:
|
1863
1967
|
col_db_type = cols_types[0][1]
|
@@ -1877,6 +1981,8 @@ def _get_create_table_query_from_dtypes(
|
|
1877
1981
|
query += f"\n {col_name} {col_db_type} {auto_increment_str} PRIMARY KEY,"
|
1878
1982
|
elif flavor == 'timescaledb' and datetime_column and datetime_column != primary_key:
|
1879
1983
|
query += f"\n {col_name} {col_db_type}{auto_increment_str} NOT NULL,"
|
1984
|
+
elif flavor == 'mssql':
|
1985
|
+
query += f"\n {col_name} {col_db_type}{auto_increment_str} NOT NULL,"
|
1880
1986
|
else:
|
1881
1987
|
query += f"\n {col_name} {col_db_type} PRIMARY KEY{auto_increment_str} NOT NULL,"
|
1882
1988
|
|
@@ -1892,6 +1998,10 @@ def _get_create_table_query_from_dtypes(
|
|
1892
1998
|
and datetime_column != primary_key
|
1893
1999
|
):
|
1894
2000
|
query += f"\n PRIMARY KEY({datetime_column_name}, {primary_key_name}),"
|
2001
|
+
|
2002
|
+
if flavor == 'mssql' and primary_key:
|
2003
|
+
query += f"\n CONSTRAINT {primary_key_constraint_name} PRIMARY KEY {primary_key_clustered} ({primary_key_name}),"
|
2004
|
+
|
1895
2005
|
query = query[:-1]
|
1896
2006
|
query += "\n)"
|
1897
2007
|
|
@@ -1912,12 +2022,11 @@ def _get_create_table_query_from_cte(
|
|
1912
2022
|
Create a new table from a CTE query.
|
1913
2023
|
"""
|
1914
2024
|
import textwrap
|
1915
|
-
from meerschaum.utils.dtypes.sql import AUTO_INCREMENT_COLUMN_FLAVORS
|
1916
2025
|
create_cte = 'create_query'
|
1917
2026
|
create_cte_name = sql_item_name(create_cte, flavor, None)
|
1918
2027
|
new_table_name = sql_item_name(new_table, flavor, schema)
|
1919
2028
|
primary_key_constraint_name = (
|
1920
|
-
sql_item_name(f'
|
2029
|
+
sql_item_name(f'PK_{new_table}', flavor, None)
|
1921
2030
|
if primary_key
|
1922
2031
|
else None
|
1923
2032
|
)
|
@@ -1926,6 +2035,7 @@ def _get_create_table_query_from_cte(
|
|
1926
2035
|
if primary_key
|
1927
2036
|
else None
|
1928
2037
|
)
|
2038
|
+
primary_key_clustered = "CLUSTERED" if not datetime_column else "NONCLUSTERED"
|
1929
2039
|
datetime_column_name = (
|
1930
2040
|
sql_item_name(datetime_column, flavor)
|
1931
2041
|
if datetime_column
|
@@ -1933,7 +2043,7 @@ def _get_create_table_query_from_cte(
|
|
1933
2043
|
)
|
1934
2044
|
if flavor in ('mssql',):
|
1935
2045
|
query = query.lstrip()
|
1936
|
-
if
|
2046
|
+
if query.lower().startswith('with '):
|
1937
2047
|
final_select_ix = query.lower().rfind('select')
|
1938
2048
|
create_table_query = (
|
1939
2049
|
query[:final_select_ix].rstrip() + ',\n'
|
@@ -1951,7 +2061,7 @@ def _get_create_table_query_from_cte(
|
|
1951
2061
|
|
1952
2062
|
alter_type_query = f"""
|
1953
2063
|
ALTER TABLE {new_table_name}
|
1954
|
-
ADD CONSTRAINT {primary_key_constraint_name} PRIMARY KEY ({primary_key_name})
|
2064
|
+
ADD CONSTRAINT {primary_key_constraint_name} PRIMARY KEY {primary_key_clustered} ({primary_key_name})
|
1955
2065
|
"""
|
1956
2066
|
elif flavor in (None,):
|
1957
2067
|
create_table_query = f"""
|
@@ -1999,11 +2109,11 @@ def _get_create_table_query_from_cte(
|
|
1999
2109
|
ADD PRIMARY KEY ({primary_key_name})
|
2000
2110
|
"""
|
2001
2111
|
|
2002
|
-
create_table_query = textwrap.dedent(create_table_query)
|
2112
|
+
create_table_query = textwrap.dedent(create_table_query).lstrip().rstrip()
|
2003
2113
|
if not primary_key:
|
2004
2114
|
return [create_table_query]
|
2005
2115
|
|
2006
|
-
alter_type_query = textwrap.dedent(alter_type_query)
|
2116
|
+
alter_type_query = textwrap.dedent(alter_type_query).lstrip().rstrip()
|
2007
2117
|
|
2008
2118
|
return [
|
2009
2119
|
create_table_query,
|
meerschaum/utils/venv/_Venv.py
CHANGED
@@ -34,10 +34,10 @@ class Venv:
|
|
34
34
|
"""
|
35
35
|
|
36
36
|
def __init__(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
self,
|
38
|
+
venv: Union[str, 'meerschaum.plugins.Plugin', None] = 'mrsm',
|
39
|
+
debug: bool = False,
|
40
|
+
) -> None:
|
41
41
|
from meerschaum.utils.venv import activate_venv, deactivate_venv, active_venvs
|
42
42
|
### For some weird threading issue,
|
43
43
|
### we can't use `isinstance` here.
|