From 8885bfc3b51c09c1d1a502a321eb5ed293833753 Mon Sep 17 00:00:00 2001 From: azban Date: Thu, 27 Jul 2023 18:25:47 -0700 Subject: [PATCH 01/11] add PARTITION BY option for CopyInto --- src/snowflake/sqlalchemy/base.py | 11 +++++++---- src/snowflake/sqlalchemy/custom_commands.py | 11 ++++++++--- tests/test_copy.py | 10 ++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index a1e16062..ef665590 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -563,7 +563,6 @@ def visit_copy_into(self, copy_into, **kw): if isinstance(copy_into.into, Table) else copy_into.into._compiler_dispatch(self, **kw) ) - from_ = None if isinstance(copy_into.from_, Table): from_ = copy_into.from_ # this is intended to catch AWSBucket and AzureContainer @@ -576,6 +575,11 @@ def visit_copy_into(self, copy_into, **kw): # everything else (selects, etc.) else: from_ = f"({copy_into.from_._compiler_dispatch(self, **kw)})" + + partition_by = "" + if copy_into.partition_by is not None: + partition_by = f"PARTITION BY {copy_into.partition_by}" + credentials, encryption = "", "" if isinstance(into, tuple): into, credentials, encryption = into @@ -586,8 +590,7 @@ def visit_copy_into(self, copy_into, **kw): options_list.sort(key=operator.itemgetter(0)) options = ( ( - " " - + " ".join( + " ".join( [ "{} = {}".format( n, @@ -608,7 +611,7 @@ def visit_copy_into(self, copy_into, **kw): options += f" {credentials}" if encryption: options += f" {encryption}" - return f"COPY INTO {into} FROM {from_} {formatter}{options}" + return f"COPY INTO {into} FROM {' '.join([from_, partition_by, formatter, options])}" def visit_copy_formatter(self, formatter, **kw): options_list = list(formatter.options.items()) diff --git a/src/snowflake/sqlalchemy/custom_commands.py b/src/snowflake/sqlalchemy/custom_commands.py index 15585bd5..4aa88a7c 100644 --- a/src/snowflake/sqlalchemy/custom_commands.py +++ b/src/snowflake/sqlalchemy/custom_commands.py @@ -115,18 +115,23 @@ class CopyInto(UpdateBase): __visit_name__ = "copy_into" _bind = None - def __init__(self, from_, into, formatter=None): + def __init__(self, from_, into, partition_by=None, formatter=None): self.from_ = from_ self.into = into self.formatter = formatter self.copy_options = {} + self.partition_by = partition_by def __repr__(self): """ repr for debugging / logging purposes only. For compilation logic, see the corresponding visitor in base.py """ - return f"COPY INTO {self.into} FROM {repr(self.from_)} {repr(self.formatter)} ({self.copy_options})" + val = f"COPY INTO {self.into} FROM {repr(self.from_)}" + if self.partition_by is not None: + val += f" PARTITION BY {self.partition_by}" + + return val + f" {repr(self.formatter)} ({self.copy_options})" def bind(self): return None @@ -533,7 +538,7 @@ def __repr__(self): ) def credentials( - self, aws_role=None, aws_key_id=None, aws_secret_key=None, aws_token=None + self, aws_role=None, aws_key_id=None, aws_secret_key=None, aws_token=None ): if aws_role is None and (aws_key_id is None and aws_secret_key is None): raise ValueError( diff --git a/tests/test_copy.py b/tests/test_copy.py index e0752d4f..92e38160 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -151,6 +151,16 @@ def test_copy_into_location(engine_testaccount, sql_compiler): == "COPY INTO @name.stage_name/prefix/file FROM python_tests_foods FILE_FORMAT=(TYPE=csv)" ) + copy_stmt_7 = CopyIntoStorage( + from_=food_items, + into=ExternalStage(name="stage_name"), + partition_by="('YEAR=' || year)" + ) + assert ( + sql_compiler(copy_stmt_7) + == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY ('YEAR=' || year)" + ) + # NOTE Other than expect known compiled text, submit it to RegressionTests environment and expect them to fail, but # because of the right reasons acceptable_exc_reasons = { From 49f8d19b66179533e9d42b8642c6204d95aceb20 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Tue, 12 Nov 2024 14:46:09 -0600 Subject: [PATCH 02/11] Fix from as Table error in copy into partition by --- DESCRIPTION.md | 1 + src/snowflake/sqlalchemy/base.py | 2 +- tests/test_copy.py | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION.md b/DESCRIPTION.md index e39984b7..a00184cc 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -19,6 +19,7 @@ Source code is also available at: - Add support for iceberg table with Snowflake Catalog - Fix cluster by option to support explicit expressions - Add support for MAP datatype + - Add support for partition by to copy into - v1.6.1(July 9, 2024) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index ef665590..b94a6005 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -564,7 +564,7 @@ def visit_copy_into(self, copy_into, **kw): else copy_into.into._compiler_dispatch(self, **kw) ) if isinstance(copy_into.from_, Table): - from_ = copy_into.from_ + from_ = copy_into.from_.name # this is intended to catch AWSBucket and AzureContainer elif ( isinstance(copy_into.from_, AWSBucket) diff --git a/tests/test_copy.py b/tests/test_copy.py index 92e38160..4010ffdb 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -151,13 +151,13 @@ def test_copy_into_location(engine_testaccount, sql_compiler): == "COPY INTO @name.stage_name/prefix/file FROM python_tests_foods FILE_FORMAT=(TYPE=csv)" ) - copy_stmt_7 = CopyIntoStorage( + copy_stmt_8 = CopyIntoStorage( from_=food_items, into=ExternalStage(name="stage_name"), - partition_by="('YEAR=' || year)" + partition_by="('YEAR=' || year)", ) assert ( - sql_compiler(copy_stmt_7) + sql_compiler(copy_stmt_8) == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY ('YEAR=' || year)" ) From bc36d4967c4ba19fabff4aef0b255aede43d3515 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Thu, 14 Nov 2024 14:09:53 -0600 Subject: [PATCH 03/11] Fix spacing in test cases --- tests/test_copy.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/test_copy.py b/tests/test_copy.py index 4010ffdb..0eda1073 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -58,8 +58,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler): ) assert ( sql_compiler(copy_stmt_1) - == "COPY INTO 's3://backup' FROM python_tests_foods FILE_FORMAT=(TYPE=csv " - "ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|') ENCRYPTION=" + == "COPY INTO 's3://backup' FROM python_tests_foods FILE_FORMAT=(TYPE=csv " + "ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|') ENCRYPTION=" "(KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab' TYPE='AWS_SSE_KMS')" ) copy_stmt_2 = CopyIntoStorage( @@ -73,8 +73,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler): sql_compiler(copy_stmt_2) == "COPY INTO 's3://backup' FROM (SELECT python_tests_foods.id, " "python_tests_foods.name, python_tests_foods.quantity FROM python_tests_foods " - "WHERE python_tests_foods.id = 1) FILE_FORMAT=(TYPE=json COMPRESSION='zstd' " - "FILE_EXTENSION='json') CREDENTIALS=(AWS_ROLE='some_iam_role') " + "WHERE python_tests_foods.id = 1) FILE_FORMAT=(TYPE=json COMPRESSION='zstd' " + "FILE_EXTENSION='json') CREDENTIALS=(AWS_ROLE='some_iam_role') " "ENCRYPTION=(TYPE='AWS_SSE_S3')" ) copy_stmt_3 = CopyIntoStorage( @@ -87,7 +87,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler): assert ( sql_compiler(copy_stmt_3) == "COPY INTO 'azure://snowflake.blob.core.windows.net/snowpile/backup' " - "FROM python_tests_foods FILE_FORMAT=(TYPE=parquet SNAPPY_COMPRESSION=true) " + "FROM python_tests_foods FILE_FORMAT=(TYPE=parquet SNAPPY_COMPRESSION=true) " "CREDENTIALS=(AZURE_SAS_TOKEN='token')" ) @@ -95,7 +95,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler): assert ( sql_compiler(copy_stmt_3) == "COPY INTO 'azure://snowflake.blob.core.windows.net/snowpile/backup' " - "FROM python_tests_foods FILE_FORMAT=(TYPE=parquet SNAPPY_COMPRESSION=true) " + "FROM python_tests_foods FILE_FORMAT=(TYPE=parquet SNAPPY_COMPRESSION=true) " "MAX_FILE_SIZE = 50000000 " "CREDENTIALS=(AZURE_SAS_TOKEN='token')" ) @@ -112,8 +112,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler): ) assert ( sql_compiler(copy_stmt_4) - == "COPY INTO python_tests_foods FROM 's3://backup' FILE_FORMAT=(TYPE=csv " - "ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|') ENCRYPTION=" + == "COPY INTO python_tests_foods FROM 's3://backup' FILE_FORMAT=(TYPE=csv " + "ESCAPE=None NULL_IF=('null', 'Null') RECORD_DELIMITER='|') ENCRYPTION=" "(KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab' TYPE='AWS_SSE_KMS')" ) @@ -126,8 +126,8 @@ def test_copy_into_location(engine_testaccount, sql_compiler): ) assert ( sql_compiler(copy_stmt_5) - == "COPY INTO python_tests_foods FROM 's3://backup' FILE_FORMAT=(TYPE=csv " - "FIELD_DELIMITER=',') ENCRYPTION=" + == "COPY INTO python_tests_foods FROM 's3://backup' FILE_FORMAT=(TYPE=csv " + "FIELD_DELIMITER=',') ENCRYPTION=" "(KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab' TYPE='AWS_SSE_KMS')" ) @@ -138,7 +138,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler): ) assert ( sql_compiler(copy_stmt_6) - == "COPY INTO @stage_name FROM python_tests_foods FILE_FORMAT=(TYPE=csv)" + == "COPY INTO @stage_name FROM python_tests_foods FILE_FORMAT=(TYPE=csv) " ) copy_stmt_7 = CopyIntoStorage( @@ -148,7 +148,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler): ) assert ( sql_compiler(copy_stmt_7) - == "COPY INTO @name.stage_name/prefix/file FROM python_tests_foods FILE_FORMAT=(TYPE=csv)" + == "COPY INTO @name.stage_name/prefix/file FROM python_tests_foods FILE_FORMAT=(TYPE=csv) " ) copy_stmt_8 = CopyIntoStorage( @@ -158,7 +158,7 @@ def test_copy_into_location(engine_testaccount, sql_compiler): ) assert ( sql_compiler(copy_stmt_8) - == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY ('YEAR=' || year)" + == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY ('YEAR=' || year) " ) # NOTE Other than expect known compiled text, submit it to RegressionTests environment and expect them to fail, but @@ -241,7 +241,7 @@ def test_copy_into_storage_csv_extended(sql_compiler): result = sql_compiler(copy_into) expected = ( r"COPY INTO TEST_IMPORT " - r"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata " + r"FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata " r"FILE_FORMAT=(TYPE=csv COMPRESSION='auto' DATE_FORMAT='AUTO' " r"ERROR_ON_COLUMN_COUNT_MISMATCH=True ESCAPE=None " r"ESCAPE_UNENCLOSED_FIELD='\134' FIELD_DELIMITER=',' " @@ -298,7 +298,7 @@ def test_copy_into_storage_parquet_named_format(sql_compiler): expected = ( "COPY INTO TEST_IMPORT " "FROM (SELECT $1:COL1::number, $1:COL2::varchar " - "FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet) " + "FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet) " "FILE_FORMAT=(format_name = parquet_file_format) force = TRUE" ) assert result == expected @@ -360,7 +360,7 @@ def test_copy_into_storage_parquet_files(sql_compiler): "COPY INTO TEST_IMPORT " "FROM (SELECT $1:COL1::number, $1:COL2::varchar " "FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet " - "(file_format => parquet_file_format)) FILES = ('foo.txt','bar.txt') " + "(file_format => parquet_file_format)) FILES = ('foo.txt','bar.txt') " "FORCE = true" ) assert result == expected @@ -422,6 +422,6 @@ def test_copy_into_storage_parquet_pattern(sql_compiler): "COPY INTO TEST_IMPORT " "FROM (SELECT $1:COL1::number, $1:COL2::varchar " "FROM @ML_POC.PUBLIC.AZURE_STAGE/testdata/out.parquet " - "(file_format => parquet_file_format)) FORCE = true PATTERN = '.*csv'" + "(file_format => parquet_file_format)) FORCE = true PATTERN = '.*csv'" ) assert result == expected From 2db12026577e73994aff91ba4392cf1acbd435ac Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Thu, 14 Nov 2024 14:35:27 -0600 Subject: [PATCH 04/11] Fix file formatting --- src/snowflake/sqlalchemy/custom_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snowflake/sqlalchemy/custom_commands.py b/src/snowflake/sqlalchemy/custom_commands.py index 4aa88a7c..1b9260fe 100644 --- a/src/snowflake/sqlalchemy/custom_commands.py +++ b/src/snowflake/sqlalchemy/custom_commands.py @@ -538,7 +538,7 @@ def __repr__(self): ) def credentials( - self, aws_role=None, aws_key_id=None, aws_secret_key=None, aws_token=None + self, aws_role=None, aws_key_id=None, aws_secret_key=None, aws_token=None ): if aws_role is None and (aws_key_id is None and aws_secret_key is None): raise ValueError( From f6dd44da0196457f0f489bd78b28360ef7faba78 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Tue, 19 Nov 2024 08:33:24 -0600 Subject: [PATCH 05/11] Comply with SQL Alchemy implementation style for partition by clause --- src/snowflake/sqlalchemy/base.py | 4 +++- tests/test_copy.py | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index b94a6005..4713892e 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -577,7 +577,9 @@ def visit_copy_into(self, copy_into, **kw): from_ = f"({copy_into.from_._compiler_dispatch(self, **kw)})" partition_by = "" - if copy_into.partition_by is not None: + if isinstance(copy_into.partition_by, str): + partition_by = f"PARTITION BY '{copy_into.partition_by}'" + elif copy_into.partition_by is not None: partition_by = f"PARTITION BY {copy_into.partition_by}" credentials, encryption = "", "" diff --git a/tests/test_copy.py b/tests/test_copy.py index 0eda1073..a79969e1 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -4,7 +4,7 @@ import pytest from sqlalchemy import Column, Integer, MetaData, Sequence, String, Table -from sqlalchemy.sql import select, text +from sqlalchemy.sql import functions, select, text from snowflake.sqlalchemy import ( AWSBucket, @@ -154,13 +154,25 @@ def test_copy_into_location(engine_testaccount, sql_compiler): copy_stmt_8 = CopyIntoStorage( from_=food_items, into=ExternalStage(name="stage_name"), - partition_by="('YEAR=' || year)", + partition_by=text("('YEAR=' || year)"), ) assert ( sql_compiler(copy_stmt_8) == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY ('YEAR=' || year) " ) + copy_stmt_9 = CopyIntoStorage( + from_=food_items, + into=ExternalStage(name="stage_name"), + partition_by=functions.concat( + text("'YEAR='"), text(food_items.columns["name"].name) + ), + ) + assert ( + sql_compiler(copy_stmt_9) + == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY concat('YEAR=', name) " + ) + # NOTE Other than expect known compiled text, submit it to RegressionTests environment and expect them to fail, but # because of the right reasons acceptable_exc_reasons = { From c84d27f003f56648f686cd93ca1a8fa8b7fcce66 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Tue, 19 Nov 2024 14:13:12 -0600 Subject: [PATCH 06/11] Modify a bit how partition by clause handles BindParameter and Executable objects --- src/snowflake/sqlalchemy/base.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index 4713892e..a462ad61 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -7,6 +7,7 @@ import re from typing import List +from sqlalchemy import Executable from sqlalchemy import exc as sa_exc from sqlalchemy import inspect, sql from sqlalchemy import util as sa_util @@ -16,7 +17,7 @@ from sqlalchemy.schema import Sequence, Table from sqlalchemy.sql import compiler, expression, functions from sqlalchemy.sql.base import CompileState -from sqlalchemy.sql.elements import quoted_name +from sqlalchemy.sql.elements import BindParameter, quoted_name from sqlalchemy.sql.selectable import Lateral, SelectState from snowflake.sqlalchemy._constants import DIALECT_NAME @@ -576,11 +577,17 @@ def visit_copy_into(self, copy_into, **kw): else: from_ = f"({copy_into.from_._compiler_dispatch(self, **kw)})" - partition_by = "" - if isinstance(copy_into.partition_by, str): - partition_by = f"PARTITION BY '{copy_into.partition_by}'" + partition_by_value = "" + if isinstance(copy_into.partition_by, (BindParameter, Executable)): + partition_by_value = copy_into.partition_by.compile( + compile_kwargs={"literal_binds": True} + ) elif copy_into.partition_by is not None: - partition_by = f"PARTITION BY {copy_into.partition_by}" + partition_by_value = copy_into.partition_by + + partition_by = ( + f"PARTITION BY {partition_by_value}" if partition_by_value != "" else "" + ) credentials, encryption = "", "" if isinstance(into, tuple): From 289fbe9f9bcbe6b05e5ff33198434e954fb23d02 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Tue, 19 Nov 2024 14:42:53 -0600 Subject: [PATCH 07/11] Fix import for Sql Alchemy 1.4 --- src/snowflake/sqlalchemy/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index a462ad61..8ab6b122 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -7,7 +7,6 @@ import re from typing import List -from sqlalchemy import Executable from sqlalchemy import exc as sa_exc from sqlalchemy import inspect, sql from sqlalchemy import util as sa_util @@ -18,6 +17,7 @@ from sqlalchemy.sql import compiler, expression, functions from sqlalchemy.sql.base import CompileState from sqlalchemy.sql.elements import BindParameter, quoted_name +from sqlalchemy.sql.expression import Executable from sqlalchemy.sql.selectable import Lateral, SelectState from snowflake.sqlalchemy._constants import DIALECT_NAME From 58eb125550b047a44c4c53bb1cfd9cfb115c3136 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Wed, 20 Nov 2024 14:24:19 -0600 Subject: [PATCH 08/11] Validate partition by value with None instead --- src/snowflake/sqlalchemy/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index 8ab6b122..7ba28e21 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -577,7 +577,7 @@ def visit_copy_into(self, copy_into, **kw): else: from_ = f"({copy_into.from_._compiler_dispatch(self, **kw)})" - partition_by_value = "" + partition_by_value = None if isinstance(copy_into.partition_by, (BindParameter, Executable)): partition_by_value = copy_into.partition_by.compile( compile_kwargs={"literal_binds": True} @@ -586,7 +586,9 @@ def visit_copy_into(self, copy_into, **kw): partition_by_value = copy_into.partition_by partition_by = ( - f"PARTITION BY {partition_by_value}" if partition_by_value != "" else "" + f"PARTITION BY {partition_by_value}" + if partition_by_value is not None + else "" ) credentials, encryption = "", "" From ac126a860b68a019cf7a0c05616cf796549e601f Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Wed, 20 Nov 2024 14:46:23 -0600 Subject: [PATCH 09/11] Validate partition by value with empty string as well --- src/snowflake/sqlalchemy/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snowflake/sqlalchemy/base.py b/src/snowflake/sqlalchemy/base.py index 7ba28e21..02e4f741 100644 --- a/src/snowflake/sqlalchemy/base.py +++ b/src/snowflake/sqlalchemy/base.py @@ -587,7 +587,7 @@ def visit_copy_into(self, copy_into, **kw): partition_by = ( f"PARTITION BY {partition_by_value}" - if partition_by_value is not None + if partition_by_value is not None and partition_by_value != "" else "" ) From d37435448807da834ed03d79780c6c7e74410ad5 Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Wed, 20 Nov 2024 14:51:24 -0600 Subject: [PATCH 10/11] Add extra case for empty string partition by --- tests/test_copy.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_copy.py b/tests/test_copy.py index a79969e1..8dfcf286 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -173,6 +173,15 @@ def test_copy_into_location(engine_testaccount, sql_compiler): == "COPY INTO @stage_name FROM python_tests_foods PARTITION BY concat('YEAR=', name) " ) + copy_stmt_10 = CopyIntoStorage( + from_=food_items, + into=ExternalStage(name="stage_name"), + partition_by="", + ) + assert ( + sql_compiler(copy_stmt_10) == "COPY INTO @stage_name FROM python_tests_foods " + ) + # NOTE Other than expect known compiled text, submit it to RegressionTests environment and expect them to fail, but # because of the right reasons acceptable_exc_reasons = { From 300a4fb672984309698c059840e6aaf24193268a Mon Sep 17 00:00:00 2001 From: Luis Calderon Achio Date: Thu, 21 Nov 2024 09:50:10 -0600 Subject: [PATCH 11/11] Move change to unreleased section --- DESCRIPTION.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION.md b/DESCRIPTION.md index a00184cc..82ddebc9 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -9,6 +9,9 @@ Source code is also available at: # Release Notes +- (Unreleased) + - Add support for partition by to copy into + - v1.7.0(November 22, 2024) - Add support for dynamic tables and required options @@ -19,7 +22,6 @@ Source code is also available at: - Add support for iceberg table with Snowflake Catalog - Fix cluster by option to support explicit expressions - Add support for MAP datatype - - Add support for partition by to copy into - v1.6.1(July 9, 2024)