meerschaum 2.7.2__py3-none-any.whl → 2.7.4__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.
- meerschaum/_internal/arguments/_parse_arguments.py +2 -0
- meerschaum/_internal/arguments/_parser.py +17 -11
- meerschaum/actions/clear.py +1 -1
- meerschaum/actions/edit.py +1 -1
- meerschaum/actions/start.py +2 -2
- meerschaum/actions/verify.py +18 -21
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/sql/_fetch.py +45 -26
- meerschaum/connectors/sql/_instance.py +4 -4
- meerschaum/connectors/sql/_pipes.py +135 -103
- meerschaum/core/Pipe/_attributes.py +1 -1
- meerschaum/core/Pipe/_dtypes.py +9 -9
- meerschaum/core/Pipe/_fetch.py +2 -3
- meerschaum/core/Pipe/_sync.py +11 -3
- meerschaum/core/Pipe/_verify.py +9 -5
- meerschaum/jobs/__init__.py +1 -3
- meerschaum/utils/daemon/Daemon.py +1 -1
- meerschaum/utils/daemon/StdinFile.py +4 -1
- meerschaum/utils/dataframe.py +10 -2
- meerschaum/utils/dtypes/sql.py +1 -1
- meerschaum/utils/formatting/__init__.py +5 -25
- meerschaum/utils/formatting/_pipes.py +9 -6
- meerschaum/utils/sql.py +156 -87
- meerschaum/utils/venv/__init__.py +61 -13
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/METADATA +1 -1
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/RECORD +32 -32
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.2.dist-info → meerschaum-2.7.4.dist-info}/zip-safe +0 -0
    
        meerschaum/utils/sql.py
    CHANGED
    
    | @@ -45,12 +45,12 @@ DROP_IF_EXISTS_FLAVORS = { | |
| 45 45 | 
             
            }
         | 
| 46 46 | 
             
            SKIP_AUTO_INCREMENT_FLAVORS = {'citus', 'duckdb'}
         | 
| 47 47 | 
             
            COALESCE_UNIQUE_INDEX_FLAVORS = {'timescaledb', 'postgresql', 'citus'}
         | 
| 48 | 
            -
             | 
| 48 | 
            +
            UPDATE_QUERIES = {
         | 
| 49 49 | 
             
                'default': """
         | 
| 50 50 | 
             
                    UPDATE {target_table_name} AS f
         | 
| 51 51 | 
             
                    {sets_subquery_none}
         | 
| 52 52 | 
             
                    FROM {target_table_name} AS t
         | 
| 53 | 
            -
                    INNER JOIN (SELECT  | 
| 53 | 
            +
                    INNER JOIN (SELECT {patch_cols_str} FROM {patch_table_name}) AS p
         | 
| 54 54 | 
             
                        ON
         | 
| 55 55 | 
             
                            {and_subquery_t}
         | 
| 56 56 | 
             
                    WHERE
         | 
| @@ -84,7 +84,7 @@ update_queries = { | |
| 84 84 | 
             
                """,
         | 
| 85 85 | 
             
                'mysql': """
         | 
| 86 86 | 
             
                    UPDATE {target_table_name} AS f
         | 
| 87 | 
            -
                    JOIN (SELECT  | 
| 87 | 
            +
                    JOIN (SELECT {patch_cols_str} FROM {patch_table_name}) AS p
         | 
| 88 88 | 
             
                    ON
         | 
| 89 89 | 
             
                        {and_subquery_f}
         | 
| 90 90 | 
             
                    {sets_subquery_f}
         | 
| @@ -100,7 +100,7 @@ update_queries = { | |
| 100 100 | 
             
                """,
         | 
| 101 101 | 
             
                'mariadb': """
         | 
| 102 102 | 
             
                    UPDATE {target_table_name} AS f
         | 
| 103 | 
            -
                    JOIN (SELECT  | 
| 103 | 
            +
                    JOIN (SELECT {patch_cols_str} FROM {patch_table_name}) AS p
         | 
| 104 104 | 
             
                    ON
         | 
| 105 105 | 
             
                        {and_subquery_f}
         | 
| 106 106 | 
             
                    {sets_subquery_f}
         | 
| @@ -179,13 +179,13 @@ update_queries = { | |
| 179 179 | 
             
                    WHERE ROWID IN (
         | 
| 180 180 | 
             
                        SELECT t.ROWID
         | 
| 181 181 | 
             
                        FROM {target_table_name} AS t
         | 
| 182 | 
            -
                        INNER JOIN (SELECT  | 
| 182 | 
            +
                        INNER JOIN (SELECT * FROM {patch_table_name}) AS p
         | 
| 183 183 | 
             
                            ON {and_subquery_t}
         | 
| 184 184 | 
             
                    );
         | 
| 185 185 | 
             
                    """,
         | 
| 186 186 | 
             
                    """
         | 
| 187 187 | 
             
                    INSERT INTO {target_table_name} AS f
         | 
| 188 | 
            -
                    SELECT  | 
| 188 | 
            +
                    SELECT {patch_cols_str} FROM {patch_table_name} AS p
         | 
| 189 189 | 
             
                    """,
         | 
| 190 190 | 
             
                ],
         | 
| 191 191 | 
             
            }
         | 
| @@ -573,21 +573,21 @@ def dateadd_str( | |
| 573 573 | 
             
                Examples
         | 
| 574 574 | 
             
                --------
         | 
| 575 575 | 
             
                >>> dateadd_str(
         | 
| 576 | 
            -
                ...     flavor | 
| 577 | 
            -
                ...     begin | 
| 578 | 
            -
                ...     number | 
| 576 | 
            +
                ...     flavor='mssql',
         | 
| 577 | 
            +
                ...     begin=datetime(2022, 1, 1, 0, 0),
         | 
| 578 | 
            +
                ...     number=1,
         | 
| 579 579 | 
             
                ... )
         | 
| 580 580 | 
             
                "DATEADD(day, 1, CAST('2022-01-01 00:00:00' AS DATETIME2))"
         | 
| 581 581 | 
             
                >>> dateadd_str(
         | 
| 582 | 
            -
                ...     flavor | 
| 583 | 
            -
                ...     begin | 
| 584 | 
            -
                ...     number | 
| 582 | 
            +
                ...     flavor='postgresql',
         | 
| 583 | 
            +
                ...     begin=datetime(2022, 1, 1, 0, 0),
         | 
| 584 | 
            +
                ...     number=1,
         | 
| 585 585 | 
             
                ... )
         | 
| 586 586 | 
             
                "CAST('2022-01-01 00:00:00' AS TIMESTAMP) + INTERVAL '1 day'"
         | 
| 587 587 |  | 
| 588 588 | 
             
                """
         | 
| 589 589 | 
             
                from meerschaum.utils.packages import attempt_import
         | 
| 590 | 
            -
                from meerschaum.utils.dtypes.sql import get_db_type_from_pd_type
         | 
| 590 | 
            +
                from meerschaum.utils.dtypes.sql import get_db_type_from_pd_type, get_pd_type_from_db_type
         | 
| 591 591 | 
             
                dateutil_parser = attempt_import('dateutil.parser')
         | 
| 592 592 | 
             
                if 'int' in str(type(begin)).lower():
         | 
| 593 593 | 
             
                    return str(begin)
         | 
| @@ -619,7 +619,14 @@ def dateadd_str( | |
| 619 619 | 
             
                        else f"'{begin}'"
         | 
| 620 620 | 
             
                    )
         | 
| 621 621 |  | 
| 622 | 
            -
                dt_is_utc =  | 
| 622 | 
            +
                dt_is_utc = (
         | 
| 623 | 
            +
                    begin_time.tzinfo is not None
         | 
| 624 | 
            +
                    if begin_time is not None
         | 
| 625 | 
            +
                    else ('+' in str(begin) or '-' in str(begin).split(':', maxsplit=1)[-1])
         | 
| 626 | 
            +
                )
         | 
| 627 | 
            +
                if db_type:
         | 
| 628 | 
            +
                    db_type_is_utc = 'utc' in get_pd_type_from_db_type(db_type).lower()
         | 
| 629 | 
            +
                    dt_is_utc = dt_is_utc or db_type_is_utc
         | 
| 623 630 | 
             
                db_type = db_type or get_db_type_from_pd_type(
         | 
| 624 631 | 
             
                    ('datetime64[ns, UTC]' if dt_is_utc else 'datetime64[ns]'),
         | 
| 625 632 | 
             
                    flavor=flavor,
         | 
| @@ -629,22 +636,32 @@ def dateadd_str( | |
| 629 636 | 
             
                if flavor in ('postgresql', 'timescaledb', 'cockroachdb', 'citus'):
         | 
| 630 637 | 
             
                    begin = (
         | 
| 631 638 | 
             
                        f"CAST({begin} AS {db_type})" if begin != 'now'
         | 
| 632 | 
            -
                        else "CAST(NOW() AT TIME ZONE 'utc' AS {db_type})"
         | 
| 639 | 
            +
                        else f"CAST(NOW() AT TIME ZONE 'utc' AS {db_type})"
         | 
| 633 640 | 
             
                    )
         | 
| 641 | 
            +
                    if dt_is_utc:
         | 
| 642 | 
            +
                        begin += " AT TIME ZONE 'UTC'"
         | 
| 634 643 | 
             
                    da = begin + (f" + INTERVAL '{number} {datepart}'" if number != 0 else '')
         | 
| 635 644 |  | 
| 636 645 | 
             
                elif flavor == 'duckdb':
         | 
| 637 646 | 
             
                    begin = f"CAST({begin} AS {db_type})" if begin != 'now' else 'NOW()'
         | 
| 647 | 
            +
                    if dt_is_utc:
         | 
| 648 | 
            +
                        begin += " AT TIME ZONE 'UTC'"
         | 
| 638 649 | 
             
                    da = begin + (f" + INTERVAL '{number} {datepart}'" if number != 0 else '')
         | 
| 639 650 |  | 
| 640 651 | 
             
                elif flavor in ('mssql',):
         | 
| 641 652 | 
             
                    if begin_time and begin_time.microsecond != 0 and not dt_is_utc:
         | 
| 642 653 | 
             
                        begin = begin[:-4] + "'"
         | 
| 643 654 | 
             
                    begin = f"CAST({begin} AS {db_type})" if begin != 'now' else 'GETUTCDATE()'
         | 
| 655 | 
            +
                    if dt_is_utc:
         | 
| 656 | 
            +
                        begin += " AT TIME ZONE 'UTC'"
         | 
| 644 657 | 
             
                    da = f"DATEADD({datepart}, {number}, {begin})" if number != 0 else begin
         | 
| 645 658 |  | 
| 646 659 | 
             
                elif flavor in ('mysql', 'mariadb'):
         | 
| 647 | 
            -
                    begin =  | 
| 660 | 
            +
                    begin = (
         | 
| 661 | 
            +
                        f"CAST({begin} AS DATETIME(6))"
         | 
| 662 | 
            +
                        if begin != 'now'
         | 
| 663 | 
            +
                        else 'UTC_TIMESTAMP(6)'
         | 
| 664 | 
            +
                    )
         | 
| 648 665 | 
             
                    da = (f"DATE_ADD({begin}, INTERVAL {number} {datepart})" if number != 0 else begin)
         | 
| 649 666 |  | 
| 650 667 | 
             
                elif flavor == 'sqlite':
         | 
| @@ -653,10 +670,10 @@ def dateadd_str( | |
| 653 670 | 
             
                elif flavor == 'oracle':
         | 
| 654 671 | 
             
                    if begin == 'now':
         | 
| 655 672 | 
             
                        begin = str(
         | 
| 656 | 
            -
                            datetime.now(timezone.utc).replace(tzinfo=None).strftime('%Y:%m:%d %M:%S.%f')
         | 
| 673 | 
            +
                            datetime.now(timezone.utc).replace(tzinfo=None).strftime(r'%Y:%m:%d %M:%S.%f')
         | 
| 657 674 | 
             
                        )
         | 
| 658 675 | 
             
                    elif begin_time:
         | 
| 659 | 
            -
                        begin = str(begin_time.strftime('%Y-%m-%d %H:%M:%S.%f'))
         | 
| 676 | 
            +
                        begin = str(begin_time.strftime(r'%Y-%m-%d %H:%M:%S.%f'))
         | 
| 660 677 | 
             
                    dt_format = 'YYYY-MM-DD HH24:MI:SS.FF'
         | 
| 661 678 | 
             
                    _begin = f"'{begin}'" if begin_time else begin
         | 
| 662 679 | 
             
                    da = (
         | 
| @@ -691,7 +708,7 @@ def test_connection( | |
| 691 708 | 
             
                    warnings.filterwarnings('ignore', 'Could not')
         | 
| 692 709 | 
             
                    try:
         | 
| 693 710 | 
             
                        return retry_connect(**_default_kw)
         | 
| 694 | 
            -
                    except Exception | 
| 711 | 
            +
                    except Exception:
         | 
| 695 712 | 
             
                        return False
         | 
| 696 713 |  | 
| 697 714 |  | 
| @@ -784,7 +801,7 @@ def sql_item_name(item: str, flavor: str, schema: Optional[str] = None) -> str: | |
| 784 801 | 
             
                    ### NOTE: System-reserved words must be quoted.
         | 
| 785 802 | 
             
                    if truncated_item.lower() in (
         | 
| 786 803 | 
             
                        'float', 'varchar', 'nvarchar', 'clob',
         | 
| 787 | 
            -
                        'boolean', 'integer', 'table',
         | 
| 804 | 
            +
                        'boolean', 'integer', 'table', 'row',
         | 
| 788 805 | 
             
                    ):
         | 
| 789 806 | 
             
                        wrappers = ('"', '"')
         | 
| 790 807 | 
             
                    else:
         | 
| @@ -1494,9 +1511,9 @@ def get_update_queries( | |
| 1494 1511 | 
             
                ):
         | 
| 1495 1512 | 
             
                    flavor = 'sqlite_delete_insert'
         | 
| 1496 1513 | 
             
                flavor_key = (f'{flavor}-upsert' if upsert else flavor)
         | 
| 1497 | 
            -
                base_queries =  | 
| 1514 | 
            +
                base_queries = UPDATE_QUERIES.get(
         | 
| 1498 1515 | 
             
                    flavor_key,
         | 
| 1499 | 
            -
                     | 
| 1516 | 
            +
                    UPDATE_QUERIES['default']
         | 
| 1500 1517 | 
             
                )
         | 
| 1501 1518 | 
             
                if not isinstance(base_queries, list):
         | 
| 1502 1519 | 
             
                    base_queries = [base_queries]
         | 
| @@ -1577,6 +1594,12 @@ def get_update_queries( | |
| 1577 1594 | 
             
                    if not value_cols:
         | 
| 1578 1595 | 
             
                        return ''
         | 
| 1579 1596 |  | 
| 1597 | 
            +
                    utc_value_cols = {
         | 
| 1598 | 
            +
                        c_name
         | 
| 1599 | 
            +
                        for c_name, c_type in value_cols
         | 
| 1600 | 
            +
                        if ('utc' in get_pd_type_from_db_type(c_type).lower())
         | 
| 1601 | 
            +
                    } if flavor not in TIMEZONE_NAIVE_FLAVORS else set()
         | 
| 1602 | 
            +
             | 
| 1580 1603 | 
             
                    cast_func_cols = {
         | 
| 1581 1604 | 
             
                        c_name: (
         | 
| 1582 1605 | 
             
                            ('', '', '')
         | 
| @@ -1585,7 +1608,11 @@ def get_update_queries( | |
| 1585 1608 | 
             
                                and are_dtypes_equal(get_pd_type_from_db_type(c_type), 'bytes')
         | 
| 1586 1609 | 
             
                            )
         | 
| 1587 1610 | 
             
                            else (
         | 
| 1588 | 
            -
                                ('CAST(', f" AS {c_type.replace('_', ' ')}", ')' | 
| 1611 | 
            +
                                ('CAST(', f" AS {c_type.replace('_', ' ')}", ')' + (
         | 
| 1612 | 
            +
                                    " AT TIME ZONE 'UTC'"
         | 
| 1613 | 
            +
                                    if c_name in utc_value_cols
         | 
| 1614 | 
            +
                                    else ''
         | 
| 1615 | 
            +
                                ))
         | 
| 1589 1616 | 
             
                                if flavor != 'sqlite'
         | 
| 1590 1617 | 
             
                                else ('', '', '')
         | 
| 1591 1618 | 
             
                            )
         | 
| @@ -1865,6 +1892,7 @@ def get_create_table_queries( | |
| 1865 1892 | 
             
                flavor: str,
         | 
| 1866 1893 | 
             
                schema: Optional[str] = None,
         | 
| 1867 1894 | 
             
                primary_key: Optional[str] = None,
         | 
| 1895 | 
            +
                primary_key_db_type: Optional[str] = None,
         | 
| 1868 1896 | 
             
                autoincrement: bool = False,
         | 
| 1869 1897 | 
             
                datetime_column: Optional[str] = None,
         | 
| 1870 1898 | 
             
            ) -> List[str]:
         | 
| @@ -1889,6 +1917,9 @@ def get_create_table_queries( | |
| 1889 1917 | 
             
                primary_key: Optional[str], default None
         | 
| 1890 1918 | 
             
                    If provided, designate this column as the primary key in the new table.
         | 
| 1891 1919 |  | 
| 1920 | 
            +
                primary_key_db_type: Optional[str], default None
         | 
| 1921 | 
            +
                    If provided, alter the primary key to this type (to set NOT NULL constraint).
         | 
| 1922 | 
            +
             | 
| 1892 1923 | 
             
                autoincrement: bool, default False
         | 
| 1893 1924 | 
             
                    If `True` and `primary_key` is provided, create the `primary_key` column
         | 
| 1894 1925 | 
             
                    as an auto-incrementing integer column.
         | 
| @@ -1915,6 +1946,7 @@ def get_create_table_queries( | |
| 1915 1946 | 
             
                    flavor,
         | 
| 1916 1947 | 
             
                    schema=schema,
         | 
| 1917 1948 | 
             
                    primary_key=primary_key,
         | 
| 1949 | 
            +
                    primary_key_db_type=primary_key_db_type,
         | 
| 1918 1950 | 
             
                    autoincrement=(autoincrement and flavor not in SKIP_AUTO_INCREMENT_FLAVORS),
         | 
| 1919 1951 | 
             
                    datetime_column=datetime_column,
         | 
| 1920 1952 | 
             
                )
         | 
| @@ -1926,6 +1958,7 @@ def _get_create_table_query_from_dtypes( | |
| 1926 1958 | 
             
                flavor: str,
         | 
| 1927 1959 | 
             
                schema: Optional[str] = None,
         | 
| 1928 1960 | 
             
                primary_key: Optional[str] = None,
         | 
| 1961 | 
            +
                primary_key_db_type: Optional[str] = None,
         | 
| 1929 1962 | 
             
                autoincrement: bool = False,
         | 
| 1930 1963 | 
             
                datetime_column: Optional[str] = None,
         | 
| 1931 1964 | 
             
            ) -> List[str]:
         | 
| @@ -2015,6 +2048,7 @@ def _get_create_table_query_from_cte( | |
| 2015 2048 | 
             
                flavor: str,
         | 
| 2016 2049 | 
             
                schema: Optional[str] = None,
         | 
| 2017 2050 | 
             
                primary_key: Optional[str] = None,
         | 
| 2051 | 
            +
                primary_key_db_type: Optional[str] = None,
         | 
| 2018 2052 | 
             
                autoincrement: bool = False,
         | 
| 2019 2053 | 
             
                datetime_column: Optional[str] = None,
         | 
| 2020 2054 | 
             
            ) -> List[str]:
         | 
| @@ -2035,7 +2069,11 @@ def _get_create_table_query_from_cte( | |
| 2035 2069 | 
             
                    if primary_key
         | 
| 2036 2070 | 
             
                    else None
         | 
| 2037 2071 | 
             
                )
         | 
| 2038 | 
            -
                primary_key_clustered =  | 
| 2072 | 
            +
                primary_key_clustered = (
         | 
| 2073 | 
            +
                    "CLUSTERED"
         | 
| 2074 | 
            +
                    if not datetime_column or datetime_column == primary_key
         | 
| 2075 | 
            +
                    else "NONCLUSTERED"
         | 
| 2076 | 
            +
                )
         | 
| 2039 2077 | 
             
                datetime_column_name = (
         | 
| 2040 2078 | 
             
                    sql_item_name(datetime_column, flavor)
         | 
| 2041 2079 | 
             
                    if datetime_column
         | 
| @@ -2045,80 +2083,106 @@ def _get_create_table_query_from_cte( | |
| 2045 2083 | 
             
                    query = query.lstrip()
         | 
| 2046 2084 | 
             
                    if query.lower().startswith('with '):
         | 
| 2047 2085 | 
             
                        final_select_ix = query.lower().rfind('select')
         | 
| 2048 | 
            -
                         | 
| 2049 | 
            -
                             | 
| 2050 | 
            -
             | 
| 2051 | 
            -
             | 
| 2052 | 
            -
             | 
| 2053 | 
            -
             | 
| 2054 | 
            -
             | 
| 2086 | 
            +
                        create_table_queries = [
         | 
| 2087 | 
            +
                            (
         | 
| 2088 | 
            +
                                query[:final_select_ix].rstrip() + ',\n'
         | 
| 2089 | 
            +
                                + f"{create_cte_name} AS (\n"
         | 
| 2090 | 
            +
                                + textwrap.indent(query[final_select_ix:], '    ')
         | 
| 2091 | 
            +
                                + "\n)\n"
         | 
| 2092 | 
            +
                                + f"SELECT *\nINTO {new_table_name}\nFROM {create_cte_name}"
         | 
| 2093 | 
            +
                            ),
         | 
| 2094 | 
            +
                        ]
         | 
| 2055 2095 | 
             
                    else:
         | 
| 2056 | 
            -
                         | 
| 2057 | 
            -
                             | 
| 2058 | 
            -
             | 
| 2059 | 
            -
             | 
| 2060 | 
            -
             | 
| 2061 | 
            -
             | 
| 2062 | 
            -
             | 
| 2063 | 
            -
             | 
| 2064 | 
            -
             | 
| 2065 | 
            -
                     | 
| 2096 | 
            +
                        create_table_queries = [
         | 
| 2097 | 
            +
                            (
         | 
| 2098 | 
            +
                                "SELECT *\n"
         | 
| 2099 | 
            +
                                f"INTO {new_table_name}\n"
         | 
| 2100 | 
            +
                                f"FROM (\n{textwrap.indent(query, '    ')}\n) AS {create_cte_name}"
         | 
| 2101 | 
            +
                            ),
         | 
| 2102 | 
            +
                        ]
         | 
| 2103 | 
            +
             | 
| 2104 | 
            +
                    alter_type_queries = []
         | 
| 2105 | 
            +
                    if primary_key_db_type:
         | 
| 2106 | 
            +
                        alter_type_queries.extend([
         | 
| 2107 | 
            +
                            (
         | 
| 2108 | 
            +
                                f"ALTER TABLE {new_table_name}\n"
         | 
| 2109 | 
            +
                                f"ALTER COLUMN {primary_key_name} {primary_key_db_type} NOT NULL"
         | 
| 2110 | 
            +
                            ),
         | 
| 2111 | 
            +
                        ])
         | 
| 2112 | 
            +
                    alter_type_queries.extend([
         | 
| 2113 | 
            +
                        (
         | 
| 2114 | 
            +
                            f"ALTER TABLE {new_table_name}\n"
         | 
| 2115 | 
            +
                            f"ADD CONSTRAINT {primary_key_constraint_name} "
         | 
| 2116 | 
            +
                            f"PRIMARY KEY {primary_key_clustered} ({primary_key_name})"
         | 
| 2117 | 
            +
                        ),
         | 
| 2118 | 
            +
                    ])
         | 
| 2066 2119 | 
             
                elif flavor in (None,):
         | 
| 2067 | 
            -
                     | 
| 2068 | 
            -
                         | 
| 2069 | 
            -
             | 
| 2070 | 
            -
             | 
| 2071 | 
            -
             | 
| 2072 | 
            -
             | 
| 2120 | 
            +
                    create_table_queries = [
         | 
| 2121 | 
            +
                        (
         | 
| 2122 | 
            +
                            f"WITH {create_cte_name} AS (\n{textwrap.index(query, '    ')}\n)\n"
         | 
| 2123 | 
            +
                            f"CREATE TABLE {new_table_name} AS\n"
         | 
| 2124 | 
            +
                            "SELECT *\n"
         | 
| 2125 | 
            +
                            f"FROM {create_cte_name}"
         | 
| 2126 | 
            +
                        ),
         | 
| 2127 | 
            +
                    ]
         | 
| 2073 2128 |  | 
| 2074 | 
            -
                     | 
| 2075 | 
            -
                         | 
| 2076 | 
            -
             | 
| 2077 | 
            -
             | 
| 2129 | 
            +
                    alter_type_queries = [
         | 
| 2130 | 
            +
                        (
         | 
| 2131 | 
            +
                            f"ALTER TABLE {new_table_name}\n"
         | 
| 2132 | 
            +
                            f"ADD PRIMARY KEY ({primary_key_name})"
         | 
| 2133 | 
            +
                        ),
         | 
| 2134 | 
            +
                    ]
         | 
| 2078 2135 | 
             
                elif flavor in ('sqlite', 'mysql', 'mariadb', 'duckdb', 'oracle'):
         | 
| 2079 | 
            -
                     | 
| 2080 | 
            -
                         | 
| 2081 | 
            -
             | 
| 2082 | 
            -
             | 
| 2083 | 
            -
             | 
| 2136 | 
            +
                    create_table_queries = [
         | 
| 2137 | 
            +
                        (
         | 
| 2138 | 
            +
                            f"CREATE TABLE {new_table_name} AS\n"
         | 
| 2139 | 
            +
                            "SELECT *\n"
         | 
| 2140 | 
            +
                            f"FROM (\n{textwrap.indent(query, '    ')}\n)"
         | 
| 2141 | 
            +
                            + (f" AS {create_cte_name}" if flavor != 'oracle' else '')
         | 
| 2142 | 
            +
                        ),
         | 
| 2143 | 
            +
                    ]
         | 
| 2084 2144 |  | 
| 2085 | 
            -
                     | 
| 2086 | 
            -
                         | 
| 2087 | 
            -
             | 
| 2088 | 
            -
             | 
| 2145 | 
            +
                    alter_type_queries = [
         | 
| 2146 | 
            +
                        (
         | 
| 2147 | 
            +
                            f"ALTER TABLE {new_table_name}\n"
         | 
| 2148 | 
            +
                            "ADD PRIMARY KEY ({primary_key_name})"
         | 
| 2149 | 
            +
                        ),
         | 
| 2150 | 
            +
                    ]
         | 
| 2089 2151 | 
             
                elif flavor == 'timescaledb' and datetime_column and datetime_column != primary_key:
         | 
| 2090 | 
            -
                     | 
| 2091 | 
            -
                         | 
| 2092 | 
            -
             | 
| 2093 | 
            -
             | 
| 2094 | 
            -
             | 
| 2152 | 
            +
                    create_table_queries = [
         | 
| 2153 | 
            +
                        (
         | 
| 2154 | 
            +
                            "SELECT *\n"
         | 
| 2155 | 
            +
                            f"INTO {new_table_name}\n"
         | 
| 2156 | 
            +
                            f"FROM (\n{textwrap.indent(query, '    ')}\n) AS {create_cte_name}\n"
         | 
| 2157 | 
            +
                        ),
         | 
| 2158 | 
            +
                    ]
         | 
| 2095 2159 |  | 
| 2096 | 
            -
                     | 
| 2097 | 
            -
                         | 
| 2098 | 
            -
             | 
| 2099 | 
            -
             | 
| 2160 | 
            +
                    alter_type_queries = [
         | 
| 2161 | 
            +
                        (
         | 
| 2162 | 
            +
                            f"ALTER TABLE {new_table_name}\n"
         | 
| 2163 | 
            +
                            f"ADD PRIMARY KEY ({datetime_column_name}, {primary_key_name})"
         | 
| 2164 | 
            +
                        ),
         | 
| 2165 | 
            +
                    ]
         | 
| 2100 2166 | 
             
                else:
         | 
| 2101 | 
            -
                     | 
| 2102 | 
            -
                         | 
| 2103 | 
            -
             | 
| 2104 | 
            -
             | 
| 2105 | 
            -
             | 
| 2167 | 
            +
                    create_table_queries = [
         | 
| 2168 | 
            +
                        (
         | 
| 2169 | 
            +
                            "SELECT *\n"
         | 
| 2170 | 
            +
                            f"INTO {new_table_name}\n"
         | 
| 2171 | 
            +
                            f"FROM (\n{textwrap.indent(query, '    ')}\n) AS {create_cte_name}"
         | 
| 2172 | 
            +
                        ),
         | 
| 2173 | 
            +
                    ]
         | 
| 2106 2174 |  | 
| 2107 | 
            -
                     | 
| 2108 | 
            -
                         | 
| 2109 | 
            -
             | 
| 2110 | 
            -
             | 
| 2175 | 
            +
                    alter_type_queries = [
         | 
| 2176 | 
            +
                        (
         | 
| 2177 | 
            +
                            f"ALTER TABLE {new_table_name}\n"
         | 
| 2178 | 
            +
                            f"ADD PRIMARY KEY ({primary_key_name})"
         | 
| 2179 | 
            +
                        ),
         | 
| 2180 | 
            +
                    ]
         | 
| 2111 2181 |  | 
| 2112 | 
            -
                create_table_query = textwrap.dedent(create_table_query).lstrip().rstrip()
         | 
| 2113 2182 | 
             
                if not primary_key:
         | 
| 2114 | 
            -
                    return  | 
| 2115 | 
            -
             | 
| 2116 | 
            -
                alter_type_query = textwrap.dedent(alter_type_query).lstrip().rstrip()
         | 
| 2183 | 
            +
                    return create_table_queries
         | 
| 2117 2184 |  | 
| 2118 | 
            -
                return  | 
| 2119 | 
            -
                    create_table_query,
         | 
| 2120 | 
            -
                    alter_type_query,
         | 
| 2121 | 
            -
                ]
         | 
| 2185 | 
            +
                return create_table_queries + alter_type_queries
         | 
| 2122 2186 |  | 
| 2123 2187 |  | 
| 2124 2188 | 
             
            def wrap_query_with_cte(
         | 
| @@ -2167,6 +2231,7 @@ def wrap_query_with_cte( | |
| 2167 2231 | 
             
                ```
         | 
| 2168 2232 |  | 
| 2169 2233 | 
             
                """
         | 
| 2234 | 
            +
                import textwrap
         | 
| 2170 2235 | 
             
                sub_query = sub_query.lstrip()
         | 
| 2171 2236 | 
             
                cte_name_quoted = sql_item_name(cte_name, flavor, None)
         | 
| 2172 2237 |  | 
| @@ -2190,7 +2255,7 @@ def wrap_query_with_cte( | |
| 2190 2255 |  | 
| 2191 2256 | 
             
                return (
         | 
| 2192 2257 | 
             
                    f"WITH {cte_name_quoted} AS (\n"
         | 
| 2193 | 
            -
                    f" | 
| 2258 | 
            +
                    f"{textwrap.indent(sub_query, '    ')}\n"
         | 
| 2194 2259 | 
             
                    f")\n{parent_query}"
         | 
| 2195 2260 | 
             
                )
         | 
| 2196 2261 |  | 
| @@ -2269,6 +2334,8 @@ def session_execute( | |
| 2269 2334 | 
             
                    queries = [queries]
         | 
| 2270 2335 | 
             
                successes, msgs, results = [], [], []
         | 
| 2271 2336 | 
             
                for query in queries:
         | 
| 2337 | 
            +
                    if debug:
         | 
| 2338 | 
            +
                        dprint(query)
         | 
| 2272 2339 | 
             
                    query_text = sqlalchemy.text(query)
         | 
| 2273 2340 | 
             
                    fail_msg = "Failed to execute queries."
         | 
| 2274 2341 | 
             
                    try:
         | 
| @@ -2283,6 +2350,8 @@ def session_execute( | |
| 2283 2350 | 
             
                    msgs.append(query_msg)
         | 
| 2284 2351 | 
             
                    results.append(result)
         | 
| 2285 2352 | 
             
                    if not query_success:
         | 
| 2353 | 
            +
                        if debug:
         | 
| 2354 | 
            +
                            dprint("Rolling back session.")
         | 
| 2286 2355 | 
             
                        session.rollback()
         | 
| 2287 2356 | 
             
                        break
         | 
| 2288 2357 | 
             
                success, msg = all(successes), '\n'.join(msgs)
         | 
| @@ -67,7 +67,6 @@ def activate_venv( | |
| 67 67 | 
             
                        return True
         | 
| 68 68 | 
             
                import sys
         | 
| 69 69 | 
             
                import os
         | 
| 70 | 
            -
                from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
         | 
| 71 70 | 
             
                if venv is not None:
         | 
| 72 71 | 
             
                    init_venv(venv=venv, debug=debug)
         | 
| 73 72 | 
             
                with LOCKS['active_venvs']:
         | 
| @@ -379,22 +378,54 @@ def init_venv( | |
| 379 378 | 
             
                import os
         | 
| 380 379 | 
             
                import pathlib
         | 
| 381 380 | 
             
                import shutil
         | 
| 381 | 
            +
                import time
         | 
| 382 382 |  | 
| 383 383 | 
             
                from meerschaum.config.static import STATIC_CONFIG
         | 
| 384 | 
            -
                from meerschaum.config._paths import  | 
| 384 | 
            +
                from meerschaum.config._paths import (
         | 
| 385 | 
            +
                    VIRTENV_RESOURCES_PATH,
         | 
| 386 | 
            +
                    VENVS_CACHE_RESOURCES_PATH,
         | 
| 387 | 
            +
                )
         | 
| 385 388 | 
             
                from meerschaum.utils.packages import is_uv_enabled
         | 
| 386 389 |  | 
| 387 390 | 
             
                venv_path = VIRTENV_RESOURCES_PATH / venv
         | 
| 388 391 | 
             
                vtp = venv_target_path(venv=venv, allow_nonexistent=True, debug=debug)
         | 
| 389 392 | 
             
                docker_home_venv_path = pathlib.Path('/home/meerschaum/venvs/mrsm')
         | 
| 390 | 
            -
             | 
| 393 | 
            +
                lock_path = VENVS_CACHE_RESOURCES_PATH / (venv + '.lock')
         | 
| 391 394 | 
             
                work_dir_env_var = STATIC_CONFIG['environment']['work_dir']
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                def update_lock(active: bool):
         | 
| 397 | 
            +
                    try:
         | 
| 398 | 
            +
                        if not active:
         | 
| 399 | 
            +
                            if debug:
         | 
| 400 | 
            +
                                print(f"Releasing lock: '{lock_path}'")
         | 
| 401 | 
            +
                            lock_path.unlink()
         | 
| 402 | 
            +
                        else:
         | 
| 403 | 
            +
                            if debug:
         | 
| 404 | 
            +
                                print(f"Acquiring lock: '{lock_path}'")
         | 
| 405 | 
            +
                            lock_path.touch()
         | 
| 406 | 
            +
                    except Exception:
         | 
| 407 | 
            +
                        pass
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                def wait_for_lock():
         | 
| 410 | 
            +
                    max_lock_seconds = 1.0
         | 
| 411 | 
            +
                    step_sleep_seconds = 0.1
         | 
| 412 | 
            +
                    init_venv_check_start = time.perf_counter()
         | 
| 413 | 
            +
                    while (time.perf_counter() - init_venv_check_start < max_lock_seconds):
         | 
| 414 | 
            +
                        if not lock_path.exists():
         | 
| 415 | 
            +
                            break
         | 
| 416 | 
            +
             | 
| 417 | 
            +
                        if debug:
         | 
| 418 | 
            +
                            print(f"Lock exists for '{venv}', sleeping...")
         | 
| 419 | 
            +
                        time.sleep(step_sleep_seconds)
         | 
| 420 | 
            +
                    update_lock(False)
         | 
| 421 | 
            +
             | 
| 392 422 | 
             
                if (
         | 
| 393 423 | 
             
                    not force
         | 
| 394 424 | 
             
                    and venv == 'mrsm'
         | 
| 395 425 | 
             
                    and os.environ.get(work_dir_env_var, None) is not None
         | 
| 396 426 | 
             
                    and docker_home_venv_path.exists()
         | 
| 397 427 | 
             
                ):
         | 
| 428 | 
            +
                    wait_for_lock()
         | 
| 398 429 | 
             
                    shutil.move(docker_home_venv_path, venv_path)
         | 
| 399 430 | 
             
                    if verify:
         | 
| 400 431 | 
             
                        verify_venv(venv, debug=debug)
         | 
| @@ -417,10 +448,12 @@ def init_venv( | |
| 417 448 | 
             
                rename_vtp = vtp.exists() and not temp_vtp.exists()
         | 
| 418 449 |  | 
| 419 450 | 
             
                if rename_vtp:
         | 
| 420 | 
            -
                     | 
| 421 | 
            -
                        vtp | 
| 422 | 
            -
                     | 
| 423 | 
            -
             | 
| 451 | 
            +
                    if debug:
         | 
| 452 | 
            +
                        print(f"Moving '{vtp}' to '{temp_vtp}'...")
         | 
| 453 | 
            +
                    shutil.move(vtp, temp_vtp)
         | 
| 454 | 
            +
             | 
| 455 | 
            +
                wait_for_lock()
         | 
| 456 | 
            +
                update_lock(True)
         | 
| 424 457 |  | 
| 425 458 | 
             
                if uv is not None:
         | 
| 426 459 | 
             
                    _venv_success = run_python_package(
         | 
| @@ -437,7 +470,9 @@ def init_venv( | |
| 437 470 | 
             
                        _venv_success = run_python_package(
         | 
| 438 471 | 
             
                            'venv',
         | 
| 439 472 | 
             
                            [venv_path.as_posix()] + (
         | 
| 440 | 
            -
                                ['--symlinks'] | 
| 473 | 
            +
                                ['--symlinks']
         | 
| 474 | 
            +
                                if platform.system() != 'Windows'
         | 
| 475 | 
            +
                                else []
         | 
| 441 476 | 
             
                            ),
         | 
| 442 477 | 
             
                            venv=None, debug=debug
         | 
| 443 478 | 
             
                        ) == 0
         | 
| @@ -447,8 +482,14 @@ def init_venv( | |
| 447 482 | 
             
                        _venv = None
         | 
| 448 483 | 
             
                if not _venv_success:
         | 
| 449 484 | 
             
                    virtualenv = attempt_import(
         | 
| 450 | 
            -
                        'virtualenv', | 
| 451 | 
            -
                         | 
| 485 | 
            +
                        'virtualenv',
         | 
| 486 | 
            +
                        venv=None,
         | 
| 487 | 
            +
                        lazy=False,
         | 
| 488 | 
            +
                        install=(not tried_virtualenv),
         | 
| 489 | 
            +
                        warn=False,
         | 
| 490 | 
            +
                        check_update=False,
         | 
| 491 | 
            +
                        color=False,
         | 
| 492 | 
            +
                        debug=debug,
         | 
| 452 493 | 
             
                    )
         | 
| 453 494 | 
             
                    if virtualenv is None:
         | 
| 454 495 | 
             
                        print(
         | 
| @@ -456,7 +497,10 @@ def init_venv( | |
| 456 497 | 
             
                            + "Please install `virtualenv` via pip then restart Meerschaum."
         | 
| 457 498 | 
             
                        )
         | 
| 458 499 | 
             
                        if rename_vtp and temp_vtp.exists():
         | 
| 459 | 
            -
                             | 
| 500 | 
            +
                            if debug:
         | 
| 501 | 
            +
                                print(f"Moving '{temp_vtp}' back to '{vtp}'...")
         | 
| 502 | 
            +
                            shutil.move(temp_vtp, vtp)
         | 
| 503 | 
            +
                        update_lock(False)
         | 
| 460 504 | 
             
                        return False
         | 
| 461 505 |  | 
| 462 506 | 
             
                    tried_virtualenv = True
         | 
| @@ -494,15 +538,19 @@ def init_venv( | |
| 494 538 | 
             
                        import traceback
         | 
| 495 539 | 
             
                        traceback.print_exc()
         | 
| 496 540 | 
             
                        if rename_vtp and temp_vtp.exists():
         | 
| 497 | 
            -
                             | 
| 541 | 
            +
                            shutil.move(temp_vtp, vtp)
         | 
| 542 | 
            +
                        update_lock(False)
         | 
| 498 543 | 
             
                        return False
         | 
| 499 544 | 
             
                if verify:
         | 
| 500 545 | 
             
                    verify_venv(venv, debug=debug)
         | 
| 501 546 | 
             
                    verified_venvs.add(venv)
         | 
| 502 547 |  | 
| 503 548 | 
             
                if rename_vtp and temp_vtp.exists():
         | 
| 504 | 
            -
                     | 
| 549 | 
            +
                    if debug:
         | 
| 550 | 
            +
                        print(f"Cleanup: move '{temp_vtp}' back to '{vtp}'.")
         | 
| 551 | 
            +
                    shutil.move(temp_vtp, vtp)
         | 
| 505 552 |  | 
| 553 | 
            +
                update_lock(False)
         | 
| 506 554 | 
             
                return True
         | 
| 507 555 |  | 
| 508 556 |  |