Skip to content

Commit 3da0012

Browse files
authored
SNOW-2296280: Add interval_day_time_from_parts function (#3702)
1 parent b77dc73 commit 3da0012

File tree

6 files changed

+449
-6
lines changed

6 files changed

+449
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
- Added a new datatype `YearMonthIntervalType` that allows users to create intervals for datetime operations.
3333
- Added a new function `interval_year_month_from_parts` that allows users to easily create `YearMonthIntervalType` without using SQL.
3434
- Added a new datatype `DayTimeIntervalType` that allows users to create intervals for datetime operations.
35+
- Added a new function `interval_day_time_from_parts` that allows users to easily create `DayTimeIntervalType` without using SQL.
3536
- Added support for `FileOperation.list` to list files in a stage with metadata.
3637
- Added support for `FileOperation.remove` to remove files in a stage.
3738
- Added an option to specify `copy_grants` for the following `DataFrame` APIs:

docs/source/snowpark/functions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ Functions
235235
initcap
236236
insert
237237
instr
238+
interval_day_time_from_parts
238239
interval_year_month_from_parts
239240
invoker_role
240241
invoker_share

src/snowflake/snowpark/functions.py

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11000,11 +11000,10 @@ def interval_year_month_from_parts(
1100011000
"""
1100111001
ast = None
1100211002
if _emit_ast:
11003+
# Always include both parameters to match the actual function execution
1100311004
args = []
11004-
if years is not None:
11005-
args.append(years)
11006-
if months is not None:
11007-
args.append(months)
11005+
args.append(years if years is not None else lit(0))
11006+
args.append(months if months is not None else lit(0))
1100811007
ast = build_function_expr("interval_year_month_from_parts", args)
1100911008

1101011009
years_col = (
@@ -11042,6 +11041,153 @@ def get_col_name(col):
1104211041
return res
1104311042

1104411043

11044+
@private_preview(
11045+
version="1.38.0",
11046+
extra_doc_string="Type DayTimeIntervalType is currently in private preview and needs to be enabled by setting parameter `FEATURE_INTERVAL_TYPES` to `ENABLED`.",
11047+
)
11048+
@publicapi
11049+
def interval_day_time_from_parts(
11050+
days: Optional[ColumnOrName] = None,
11051+
hours: Optional[ColumnOrName] = None,
11052+
mins: Optional[ColumnOrName] = None,
11053+
secs: Optional[ColumnOrName] = None,
11054+
_emit_ast: bool = True,
11055+
) -> Column:
11056+
"""
11057+
Creates a day-time interval expression using with specified days, hours, mins and seconds.
11058+
11059+
This DayTime is not to be confused with the interval created by make_interval.
11060+
You can define a table column to be of data type DayTimeIntervalType.
11061+
11062+
Args:
11063+
days: The number of days, positive or negative
11064+
hours: The number of hours, positive or negative
11065+
mins: The number of minutes, positive or negative
11066+
secs: The number of seconds, positive or negative
11067+
11068+
Returns:
11069+
A Column representing a day-time interval
11070+
11071+
Example::
11072+
11073+
>>> from snowflake.snowpark.functions import interval_day_time_from_parts
11074+
>>>
11075+
>>> _ = session.sql("ALTER SESSION SET FEATURE_INTERVAL_TYPES=ENABLED;").collect()
11076+
>>> df = session.create_dataframe([[1, 12, 30, 01.001001]], ['day', 'hour', 'min', 'sec'])
11077+
>>> df.select(interval_day_time_from_parts(col("day"), col("hour"), col("min"), col("sec")).alias("interval")).show()
11078+
--------------------------
11079+
|"INTERVAL" |
11080+
--------------------------
11081+
|1 day, 12:30:01.001000 |
11082+
--------------------------
11083+
<BLANKLINE>
11084+
11085+
"""
11086+
# Handle AST emission
11087+
ast = None
11088+
if _emit_ast:
11089+
# Create AST for this custom function using build_function_expr
11090+
# Always include all 4 parameters to match the actual function execution
11091+
args = []
11092+
args.append(days if days is not None else lit(0))
11093+
args.append(hours if hours is not None else lit(0))
11094+
args.append(mins if mins is not None else lit(0))
11095+
args.append(secs if secs is not None else lit(0))
11096+
ast = build_function_expr("interval_day_time_from_parts", args)
11097+
11098+
days_col = (
11099+
lit(0) if days is None else _to_col_if_str(days, "interval_day_time_from_parts")
11100+
)
11101+
hours_col = (
11102+
lit(0)
11103+
if hours is None
11104+
else _to_col_if_str(hours, "interval_day_time_from_parts")
11105+
)
11106+
mins_col = (
11107+
lit(0) if mins is None else _to_col_if_str(mins, "interval_day_time_from_parts")
11108+
)
11109+
secs_col = (
11110+
lit(0) if secs is None else _to_col_if_str(secs, "interval_day_time_from_parts")
11111+
)
11112+
11113+
total_seconds = (
11114+
days_col * lit(86400) + hours_col * lit(3600) + mins_col * lit(60) + secs_col
11115+
)
11116+
11117+
is_negative = total_seconds < lit(0)
11118+
abs_total_seconds = abs(total_seconds)
11119+
11120+
days_part = cast(floor(abs_total_seconds / lit(86400)), "int")
11121+
remaining_after_days = abs_total_seconds % lit(86400)
11122+
11123+
hours_part = cast(floor(remaining_after_days / lit(3600)), "int")
11124+
remaining_after_hours = remaining_after_days % lit(3600)
11125+
11126+
mins_part = cast(floor(remaining_after_hours / lit(60)), "int")
11127+
secs_part = remaining_after_hours % lit(60)
11128+
11129+
hours_str = iff(
11130+
hours_part < lit(10),
11131+
concat(lit("0"), cast(hours_part, "str")),
11132+
cast(hours_part, "str"),
11133+
)
11134+
11135+
mins_str = iff(
11136+
mins_part < lit(10),
11137+
concat(lit("0"), cast(mins_part, "str")),
11138+
cast(mins_part, "str"),
11139+
)
11140+
11141+
secs_int = cast(floor(secs_part), "int")
11142+
secs_str = iff(
11143+
secs_int < lit(10),
11144+
concat(lit("0"), cast(secs_int, "str")),
11145+
cast(secs_int, "str"),
11146+
)
11147+
11148+
has_fraction = abs(secs_part - cast(secs_int, "double")) > 1e-10
11149+
fractional_part = secs_part - cast(secs_int, "double")
11150+
11151+
fraction_str = iff(
11152+
has_fraction,
11153+
concat(
11154+
lit("."),
11155+
lpad(
11156+
cast(round(fractional_part * lit(1000), 0), "str"),
11157+
3,
11158+
lit("0"),
11159+
),
11160+
),
11161+
lit(""),
11162+
)
11163+
11164+
secs_formatted = concat(secs_str, fraction_str)
11165+
11166+
sign_prefix = iff(is_negative, lit("-"), lit(""))
11167+
interval_value = concat(
11168+
sign_prefix,
11169+
cast(days_part, "str"),
11170+
lit(" "),
11171+
hours_str,
11172+
lit(":"),
11173+
mins_str,
11174+
lit(":"),
11175+
secs_formatted,
11176+
)
11177+
11178+
def get_col_name(col):
11179+
if isinstance(col._expr1, Literal):
11180+
return str(col._expr1.value)
11181+
else:
11182+
return str(col._expr1)
11183+
11184+
alias_name = f"interval_day_time_from_parts({get_col_name(days_col)}, {get_col_name(hours_col)}, {get_col_name(mins_col)}, {get_col_name(secs_col)})"
11185+
11186+
res = cast(interval_value, "INTERVAL DAY TO SECOND").alias(alias_name)
11187+
res._ast = ast
11188+
return res
11189+
11190+
1104511191
@publicapi
1104611192
@deprecated(
1104711193
version="1.28.0",

tests/ast/data/functions2.test

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ df351 = df.select(function("avg")("B"))
450450

451451
df352 = df.select(interval_year_month_from_parts("A", "B"))
452452

453+
df353 = df.select(interval_day_time_from_parts("A", "B", "C", "D"))
454+
453455
## EXPECTED UNPARSER OUTPUT
454456

455457
df = session.table("table1")
@@ -900,6 +902,8 @@ df351 = df.select(call_function("avg", "B"))
900902

901903
df352 = df.select(interval_year_month_from_parts("A", "B"))
902904

905+
df353 = df.select(interval_day_time_from_parts("A", "B", "C", "D"))
906+
903907
## EXPECTED ENCODED AST
904908

905909
interned_value_table {
@@ -29253,6 +29257,104 @@ body {
2925329257
uid: 224
2925429258
}
2925529259
}
29260+
body {
29261+
bind {
29262+
expr {
29263+
dataframe_select {
29264+
cols {
29265+
args {
29266+
apply_expr {
29267+
fn {
29268+
builtin_fn {
29269+
name {
29270+
name {
29271+
name_flat {
29272+
name: "interval_day_time_from_parts"
29273+
}
29274+
}
29275+
}
29276+
}
29277+
}
29278+
pos_args {
29279+
string_val {
29280+
src {
29281+
end_column: 74
29282+
end_line: 475
29283+
file: 2
29284+
start_column: 26
29285+
start_line: 475
29286+
}
29287+
v: "A"
29288+
}
29289+
}
29290+
pos_args {
29291+
string_val {
29292+
src {
29293+
end_column: 74
29294+
end_line: 475
29295+
file: 2
29296+
start_column: 26
29297+
start_line: 475
29298+
}
29299+
v: "B"
29300+
}
29301+
}
29302+
pos_args {
29303+
string_val {
29304+
src {
29305+
end_column: 74
29306+
end_line: 475
29307+
file: 2
29308+
start_column: 26
29309+
start_line: 475
29310+
}
29311+
v: "C"
29312+
}
29313+
}
29314+
pos_args {
29315+
string_val {
29316+
src {
29317+
end_column: 74
29318+
end_line: 475
29319+
file: 2
29320+
start_column: 26
29321+
start_line: 475
29322+
}
29323+
v: "D"
29324+
}
29325+
}
29326+
src {
29327+
end_column: 74
29328+
end_line: 475
29329+
file: 2
29330+
start_column: 26
29331+
start_line: 475
29332+
}
29333+
}
29334+
}
29335+
variadic: true
29336+
}
29337+
df {
29338+
dataframe_ref {
29339+
id: 1
29340+
}
29341+
}
29342+
src {
29343+
end_column: 75
29344+
end_line: 475
29345+
file: 2
29346+
start_column: 16
29347+
start_line: 475
29348+
}
29349+
}
29350+
}
29351+
first_request_id: "\003U\"\366q\366P\346\260\261?\234\303\254\316\353"
29352+
symbol {
29353+
value: "df353"
29354+
}
29355+
uid: 225
29356+
}
29357+
}
2925629358
client_ast_version: 1
2925729359
client_language {
2925829360
python_language {

tests/integ/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def session(
274274
.config("local_testing", local_testing_mode)
275275
.config(
276276
"session_parameters",
277-
{"feature_interval_types": "ENABLED", "enable_interval_subtypes": "true"},
277+
{"feature_interval_types": "ENABLED"},
278278
)
279279
.create()
280280
)

0 commit comments

Comments
 (0)