diff --git a/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py b/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py index 1833d2ffbac0..969ddf2794a5 100644 --- a/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py +++ b/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py @@ -48,6 +48,75 @@ ) +class TimeUnit(str, Enum): + """Enumeration of the different time units supported by the Firestore backend.""" + + MICROSECOND = "microsecond" + MILLISECOND = "millisecond" + SECOND = "second" + MINUTE = "minute" + HOUR = "hour" + DAY = "day" + + +class TimeGranularity(str, Enum): + """Enumeration of the different time granularities supported by the Firestore backend.""" + + # Inherit from TimeUnit + MICROSECOND = TimeUnit.MICROSECOND.value + MILLISECOND = TimeUnit.MILLISECOND.value + SECOND = TimeUnit.SECOND.value + MINUTE = TimeUnit.MINUTE.value + HOUR = TimeUnit.HOUR.value + DAY = TimeUnit.DAY.value + + # Additional granularities + WEEK = "week" + WEEK_MONDAY = "week(monday)" + WEEK_TUESDAY = "week(tuesday)" + WEEK_WEDNESDAY = "week(wednesday)" + WEEK_THURSDAY = "week(thursday)" + WEEK_FRIDAY = "week(friday)" + WEEK_SATURDAY = "week(saturday)" + WEEK_SUNDAY = "week(sunday)" + ISOWEEK = "isoweek" + MONTH = "month" + QUARTER = "quarter" + YEAR = "year" + ISOYEAR = "isoyear" + + +class TimePart(str, Enum): + """Enumeration of the different time parts supported by the Firestore backend.""" + + # Inherit from TimeUnit + MICROSECOND = TimeUnit.MICROSECOND.value + MILLISECOND = TimeUnit.MILLISECOND.value + SECOND = TimeUnit.SECOND.value + MINUTE = TimeUnit.MINUTE.value + HOUR = TimeUnit.HOUR.value + DAY = TimeUnit.DAY.value + + # Inherit from TimeGranularity + WEEK = TimeGranularity.WEEK.value + WEEK_MONDAY = TimeGranularity.WEEK_MONDAY.value + WEEK_TUESDAY = TimeGranularity.WEEK_TUESDAY.value + WEEK_WEDNESDAY = TimeGranularity.WEEK_WEDNESDAY.value + WEEK_THURSDAY = TimeGranularity.WEEK_THURSDAY.value + WEEK_FRIDAY = TimeGranularity.WEEK_FRIDAY.value + WEEK_SATURDAY = TimeGranularity.WEEK_SATURDAY.value + WEEK_SUNDAY = TimeGranularity.WEEK_SUNDAY.value + ISOWEEK = TimeGranularity.ISOWEEK.value + MONTH = TimeGranularity.MONTH.value + QUARTER = TimeGranularity.QUARTER.value + YEAR = TimeGranularity.YEAR.value + ISOYEAR = TimeGranularity.ISOYEAR.value + + # Additional parts + DAY_OF_WEEK = "dayofweek" + DAY_OF_YEAR = "dayofyear" + + class Ordering: """Represents the direction for sorting results in a pipeline.""" @@ -90,6 +159,31 @@ def _to_pb(self) -> Value: ) +class Type(str, Enum): + """Enumeration of the different types generated by the Firestore backend.""" + + NULL = "null" + ARRAY = "array" + BOOLEAN = "boolean" + BYTES = "bytes" + TIMESTAMP = "timestamp" + GEO_POINT = "geo_point" + NUMBER = "number" + INT32 = "int32" + INT64 = "int64" + FLOAT64 = "float64" + DECIMAL128 = "decimal128" + MAP = "map" + REFERENCE = "reference" + STRING = "string" + VECTOR = "vector" + MAX_KEY = "max_key" + MIN_KEY = "min_key" + OBJECT_ID = "object_id" + REGEX = "regex" + REQUEST_TIMESTAMP = "request_timestamp" + + class Expression(ABC): """Represents an expression that can be evaluated to a value within the execution of a pipeline. @@ -120,6 +214,8 @@ def _cast_to_expr_or_convert_to_constant( """Convert arbitrary object to an Expression.""" if isinstance(o, Expression): return o + if isinstance(o, Enum): + o = o.value if isinstance(o, dict): return Map(o) if isinstance(o, list): @@ -2079,20 +2175,21 @@ def unix_seconds_to_timestamp(self) -> "Expression": @expose_as_static def timestamp_add( - self, unit: Expression | str, amount: Expression | float + self, unit: TimeUnit | str | Expression, amount: Expression | float ) -> "Expression": """Creates an expression that adds a specified amount of time to this timestamp expression. Example: + >>> # Add 1.5 days to the 'timestamp' field using TimeUnit enum. + >>> Field.of("timestamp").timestamp_add(TimeUnit.DAY, 1.5) >>> # Add a duration specified by the 'unit' and 'amount' fields to the 'timestamp' field. >>> Field.of("timestamp").timestamp_add(Field.of("unit"), Field.of("amount")) - >>> # Add 1.5 days to the 'timestamp' field. + >>> # Add 1.5 days to the 'timestamp' field using a string. >>> Field.of("timestamp").timestamp_add("day", 1.5) Args: - unit: The expression or string evaluating to the unit of time to add, must be one of - 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. - amount: The expression or float representing the amount of time to add. + unit: The unit of time to add. + amount: The amount of time to add. Returns: A new `Expression` representing the resulting timestamp. @@ -2108,20 +2205,21 @@ def timestamp_add( @expose_as_static def timestamp_subtract( - self, unit: Expression | str, amount: Expression | float + self, unit: TimeUnit | str | Expression, amount: Expression | float ) -> "Expression": """Creates an expression that subtracts a specified amount of time from this timestamp expression. Example: + >>> # Subtract 2.5 hours from the 'timestamp' field using TimeUnit enum. + >>> Field.of("timestamp").timestamp_subtract(TimeUnit.HOUR, 2.5) >>> # Subtract a duration specified by the 'unit' and 'amount' fields from the 'timestamp' field. >>> Field.of("timestamp").timestamp_subtract(Field.of("unit"), Field.of("amount")) - >>> # Subtract 2.5 hours from the 'timestamp' field. + >>> # Subtract 2.5 hours from the 'timestamp' field using a string. >>> Field.of("timestamp").timestamp_subtract("hour", 2.5) Args: - unit: The expression or string evaluating to the unit of time to subtract, must be one of - 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. - amount: The expression or float representing the amount of time to subtract. + unit: The unit of time to subtract. + amount: The amount of time to subtract. Returns: A new `Expression` representing the resulting timestamp. @@ -2206,6 +2304,163 @@ def as_(self, alias: str) -> "AliasedExpression": """ return AliasedExpression(self, alias) + @expose_as_static + def cmp(self, other: Expression | CONSTANT_TYPE) -> "Expression": + """Creates an expression that compares this expression to another expression. + + Returns an integer: + * -1 if this expression is less than the other + * 0 if they are equal + * 1 if this expression is greater than the other + + Example: + >>> # Compare the 'price' field to 10 + >>> Field.of("price").cmp(10) + + Args: + other: The value to compare against. + + Returns: + A new `Expression` representing the comparison operation. + """ + return FunctionExpression( + "cmp", [self, self._cast_to_expr_or_convert_to_constant(other)] + ) + + @expose_as_static + def timestamp_trunc( + self, + granularity: TimeGranularity | Expression | str, + timezone: Expression | str | None = None, + ) -> "Expression": + """Creates an expression that truncates a timestamp to a specified granularity. + + Example: + >>> # Truncate the 'createdAt' field to the day using TimeGranularity enum + >>> Field.of("createdAt").timestamp_trunc(TimeGranularity.DAY) + >>> # Truncate the 'createdAt' field to the day in the 'America/Los_Angeles' timezone + >>> Field.of("createdAt").timestamp_trunc(TimeGranularity.DAY, "America/Los_Angeles") + >>> # Truncate the 'createdAt' field to the day using a string + >>> Field.of("createdAt").timestamp_trunc("day") + + Args: + granularity: The granularity to truncate to. + timezone: The optional timezone. + + Returns: + A new `Expression` representing the timestamp_trunc operation. + """ + args = [self, self._cast_to_expr_or_convert_to_constant(granularity)] + if timezone is not None: + args.append(self._cast_to_expr_or_convert_to_constant(timezone)) + return FunctionExpression("timestamp_trunc", args) + + @expose_as_static + def timestamp_extract( + self, + part: TimePart | str | Expression, + timezone: str | Expression | None = None, + ) -> "Expression": + """Creates an expression that extracts a part of a timestamp. + + Example: + >>> # Extract the year from the 'createdAt' field using TimePart enum + >>> Field.of("createdAt").timestamp_extract(TimePart.YEAR) + >>> # Extract the year from the 'createdAt' field in the 'America/Los_Angeles' timezone + >>> Field.of("createdAt").timestamp_extract(TimePart.YEAR, "America/Los_Angeles") + >>> # Extract the year from the 'createdAt' field using a string + >>> Field.of("createdAt").timestamp_extract("year") + + Args: + part: The part to extract. + timezone: The optional timezone. + + Returns: + A new `Expression` representing the timestamp_extract operation. + """ + args = [self, self._cast_to_expr_or_convert_to_constant(part)] + if timezone is not None: + args.append(self._cast_to_expr_or_convert_to_constant(timezone)) + return FunctionExpression("timestamp_extract", args) + + @expose_as_static + def timestamp_diff( + self, start: Expression | datetime.datetime, unit: TimeUnit | str | Expression + ) -> "Expression": + """Creates an expression that computes the difference between two timestamps in the specified unit. + + Example: + >>> # Compute the difference in days between the 'end' field and the 'start' field using TimeUnit enum + >>> Field.of("end").timestamp_diff(Field.of("start"), TimeUnit.DAY) + >>> # Compute the difference in days using a string + >>> Field.of("end").timestamp_diff(Field.of("start"), "day") + + Args: + start: The start timestamp. + unit: The unit of time. + + Returns: + A new `Expression` representing the timestamp_diff operation. + """ + return FunctionExpression( + "timestamp_diff", + [ + self, + self._cast_to_expr_or_convert_to_constant(start), + self._cast_to_expr_or_convert_to_constant(unit), + ], + ) + + @expose_as_static + def if_null(self, *others: Expression | CONSTANT_TYPE) -> "Expression": + """Creates an expression that returns the first non-null expression from the provided arguments. + + Example: + >>> # Return the 'nickname' field if not null, otherwise return 'firstName' + >>> Field.of("nickname").if_null(Field.of("firstName")) + + Args: + *others: Additional expressions or constants to evaluate if the previous ones are null. + + Returns: + A new `Expression` representing the if_null operation. + """ + return FunctionExpression( + "if_null", + [self] + [self._cast_to_expr_or_convert_to_constant(o) for o in others], + ) + + @expose_as_static + def type(self) -> "Expression": + """Creates an expression that returns the data type of this expression's result as a string. + + Example: + >>> # Get the type of the 'title' field + >>> Field.of("title").type() + + Returns: + A new `Expression` representing the type operation. + """ + return FunctionExpression("type", [self]) + + @expose_as_static + def is_type(self, type_val: Type | str | Expression) -> "BooleanExpression": + """Creates an expression that checks if the result is of the specified type. + + Example: + >>> # Check if the 'price' field is a number + >>> Field.of("price").is_type("number") + + Args: + type_val: The type string or expression to check against. + + Returns: + A new `BooleanExpression` representing the is_type operation. + """ + return BooleanExpression( + "is_type", [self, self._cast_to_expr_or_convert_to_constant(type_val)] + ) + class Constant(Expression, Generic[CONSTANT_TYPE]): """Represents a constant literal value in an expression.""" diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/date_and_time.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/date_and_time.yaml index 2319b333bf58..1e374b0c6bf4 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/date_and_time.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/date_and_time.yaml @@ -101,3 +101,175 @@ tests: from_seconds: "1993-04-28T12:01:00.000000+00:00" plus_day: "1993-04-29T12:01:00.654321+00:00" minus_hour: "1993-04-28T11:01:00.654321+00:00" + - description: testTimestampTruncExtractDiff + pipeline: + - Collection: timestamps + - Limit: 1 + - Select: + - AliasedExpression: + - FunctionExpression.timestamp_trunc: + - Field: time + - Constant: "day" + - Constant: "America/Los_Angeles" + - "trunc_day" + - AliasedExpression: + - FunctionExpression.timestamp_extract: + - Field: time + - Constant: "year" + - Constant: "America/Los_Angeles" + - "extract_year" + - AliasedExpression: + - FunctionExpression.timestamp_diff: + - FunctionExpression.timestamp_add: + - Field: time + - Constant: "day" + - Constant: 1 + - Field: time + - Constant: "hour" + - "diff_hours" + assert_results: + - trunc_day: "1993-04-28T07:00:00.000000+00:00" + extract_year: 1993 + diff_hours: 24 + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /timestamps + name: collection + - args: + - integerValue: '1' + name: limit + - args: + - mapValue: + fields: + trunc_day: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: day + - stringValue: America/Los_Angeles + name: timestamp_trunc + extract_year: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: year + - stringValue: America/Los_Angeles + name: timestamp_extract + diff_hours: + functionValue: + args: + - functionValue: + args: + - fieldReferenceValue: time + - stringValue: day + - integerValue: '1' + name: timestamp_add + - fieldReferenceValue: time + - stringValue: hour + name: timestamp_diff + name: select + - description: testTimestampExtractAndTruncAdvancedParts + pipeline: + - Collection: timestamps + - Limit: 1 + - Select: + - AliasedExpression: + - FunctionExpression.timestamp_extract: + - Field: time + - Constant: "dayofyear" + - Constant: "America/Los_Angeles" + - "extract_day_of_year" + - AliasedExpression: + - FunctionExpression.timestamp_extract: + - Field: time + - Constant: "week(sunday)" + - Constant: "America/Los_Angeles" + - "extract_week_sunday" + - AliasedExpression: + - FunctionExpression.timestamp_extract: + - Field: time + - Constant: "isoweek" + - Constant: "America/Los_Angeles" + - "extract_isoweek" + - AliasedExpression: + - FunctionExpression.timestamp_extract: + - Field: time + - Constant: "quarter" + - Constant: "America/Los_Angeles" + - "extract_quarter" + - AliasedExpression: + - FunctionExpression.timestamp_trunc: + - Field: time + - Constant: "isoweek" + - Constant: "America/Los_Angeles" + - "trunc_isoweek" + - AliasedExpression: + - FunctionExpression.timestamp_trunc: + - Field: time + - Constant: "quarter" + - Constant: "America/Los_Angeles" + - "trunc_quarter" + assert_results: + - extract_day_of_year: 118 + extract_week_sunday: 17 + extract_isoweek: 17 + extract_quarter: 2 + trunc_isoweek: "1993-04-26T07:00:00.000000+00:00" + trunc_quarter: "1993-04-01T08:00:00.000000+00:00" + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /timestamps + name: collection + - args: + - integerValue: '1' + name: limit + - args: + - mapValue: + fields: + extract_day_of_year: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: dayofyear + - stringValue: America/Los_Angeles + name: timestamp_extract + extract_week_sunday: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: week(sunday) + - stringValue: America/Los_Angeles + name: timestamp_extract + extract_isoweek: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: isoweek + - stringValue: America/Los_Angeles + name: timestamp_extract + extract_quarter: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: quarter + - stringValue: America/Los_Angeles + name: timestamp_extract + trunc_isoweek: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: isoweek + - stringValue: America/Los_Angeles + name: timestamp_trunc + trunc_quarter: + functionValue: + args: + - fieldReferenceValue: time + - stringValue: quarter + - stringValue: America/Los_Angeles + name: timestamp_trunc + name: select diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/general.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/general.yaml index 1b03beb4a3c1..b14e636d1ebc 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/general.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/general.yaml @@ -685,6 +685,27 @@ tests: - fieldReferenceValue: awards - stringValue: full_replace name: replace_with + - description: testTypeAndIsType + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Limit: 1 + - Select: + - AliasedExpression: + - FunctionExpression.type: + - Field: awards + - "val_type" + - AliasedExpression: + - FunctionExpression.is_type: + - Field: awards + - Constant: "map" + - "val_is_map" + assert_results: + - val_type: "map" + val_is_map: true - description: literals pipeline: - Literals: @@ -743,4 +764,4 @@ tests: fields: res: fieldReferenceValue: res - name: select \ No newline at end of file + name: select diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/logical.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/logical.yaml index 296cfda146c2..a3de3a4a07fe 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/logical.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/logical.yaml @@ -688,3 +688,74 @@ tests: conditional_field: "Dystopian" - title: "Dune" conditional_field: "Frank Herbert" + - description: testCmp + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - FunctionExpression.cmp: + - Field: rating + - Constant: 4.5 + - Constant: 1 + - Select: + - title + - Sort: + - Ordering: + - Field: title + - ASCENDING + assert_results: + - title: Dune + - title: The Lord of the Rings + - description: testIfNullWithNullField + pipeline: + - Collection: errors + - Where: + - FunctionExpression.equal: + - Field: value + - Constant: null + - Select: + - AliasedExpression: + - FunctionExpression.if_null: + - Field: value + - Constant: "default" + - "value_or_default" + assert_results: + - value_or_default: "default" + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /errors + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: value + - nullValue: null + name: equal + name: where + - args: + - mapValue: + fields: + value_or_default: + functionValue: + name: if_null + args: + - fieldReferenceValue: value + - stringValue: default + name: select + - description: testIfNullWithNonNullField + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "1984" + - Select: + - AliasedExpression: + - FunctionExpression.if_null: + - Field: title + - Constant: "default" + - "value_or_default" + assert_results: + - value_or_default: "1984" diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/map.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/map.yaml index 0338c570b63e..220282b1a8de 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/map.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/map.yaml @@ -279,9 +279,11 @@ tests: - Field: awards.hugo assert_results: - title: The Hitchhiker's Guide to the Galaxy - awards.hugo: true + awards: + hugo: true - title: Dune - awards.hugo: true + awards: + hugo: true assert_proto: pipeline: stages: @@ -340,10 +342,16 @@ tests: - FunctionExpression.map_keys: - Field: awards - "award_keys" + # server doesn't guarantee ordering + - Unnest: + - Field: award_keys + - Sort: + - Ordering: + - Field: award_keys + - ASCENDING assert_results: - - award_keys: - - hugo - - nebula + - award_keys: hugo + - award_keys: nebula assert_proto: pipeline: stages: @@ -366,6 +374,18 @@ tests: args: - fieldReferenceValue: awards name: select + - args: + - fieldReferenceValue: award_keys + - fieldReferenceValue: award_keys + name: unnest + - args: + - mapValue: + fields: + direction: + stringValue: ascending + expression: + fieldReferenceValue: award_keys + name: sort - description: testMapValues pipeline: - Collection: books @@ -416,12 +436,20 @@ tests: - FunctionExpression.map_entries: - Field: awards - "award_entries" + # server doesn't guarantee ordering + - Unnest: + - Field: award_entries + - Sort: + - Ordering: + - Field: award_entries.k + - ASCENDING assert_results: - award_entries: - - k: hugo - v: true - - k: nebula - v: true + k: hugo + v: true + - award_entries: + k: nebula + v: true assert_proto: pipeline: stages: @@ -444,3 +472,15 @@ tests: args: - fieldReferenceValue: awards name: select + - args: + - fieldReferenceValue: award_entries + - fieldReferenceValue: award_entries + name: unnest + - args: + - mapValue: + fields: + direction: + stringValue: ascending + expression: + fieldReferenceValue: award_entries.k + name: sort diff --git a/packages/google-cloud-firestore/tests/system/test_pipeline_acceptance.py b/packages/google-cloud-firestore/tests/system/test_pipeline_acceptance.py index f1fd1326c765..afff43ac6950 100644 --- a/packages/google-cloud-firestore/tests/system/test_pipeline_acceptance.py +++ b/packages/google-cloud-firestore/tests/system/test_pipeline_acceptance.py @@ -52,6 +52,8 @@ def yaml_loader(field="tests", dir_name="pipeline_e2e", attach_file_name=True): """ combined_yaml = None for file_name in os.listdir(f"{test_dir_name}/{dir_name}"): + if not file_name.endswith(".yaml"): + continue with open(f"{test_dir_name}/{dir_name}/{file_name}") as f: new_yaml = yaml.safe_load(f) assert new_yaml is not None, f"found empty yaml in {file_name}" diff --git a/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py b/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py index 12a41aeaa069..98db3c3a8f17 100644 --- a/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py +++ b/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py @@ -1793,6 +1793,145 @@ def test_last(self): infix_instance = arg1.last() assert infix_instance == instance + def test_cmp(self): + arg1 = self._make_arg("Value") + arg2 = self._make_arg("Other") + instance = Expression.cmp(arg1, arg2) + assert instance.name == "cmp" + assert instance.params == [arg1, arg2] + assert repr(instance) == "Value.cmp(Other)" + infix_instance = arg1.cmp(arg2) + assert infix_instance == instance + + def test_timestamp_trunc(self): + arg1 = self._make_arg("Timestamp") + arg2 = self._make_arg("Granularity") + arg3 = self._make_arg("Timezone") + instance = Expression.timestamp_trunc(arg1, arg2) + assert instance.name == "timestamp_trunc" + assert instance.params == [arg1, arg2] + assert repr(instance) == "Timestamp.timestamp_trunc(Granularity)" + infix_instance = arg1.timestamp_trunc(arg2) + assert infix_instance == instance + + instance_tz = Expression.timestamp_trunc(arg1, arg2, arg3) + assert instance_tz.name == "timestamp_trunc" + assert instance_tz.params == [arg1, arg2, arg3] + assert repr(instance_tz) == "Timestamp.timestamp_trunc(Granularity, Timezone)" + infix_instance_tz = arg1.timestamp_trunc(arg2, arg3) + assert infix_instance_tz == instance_tz + + def test_timestamp_extract(self): + arg1 = self._make_arg("Timestamp") + arg2 = self._make_arg("Part") + arg3 = self._make_arg("Timezone") + instance = Expression.timestamp_extract(arg1, arg2) + assert instance.name == "timestamp_extract" + assert instance.params == [arg1, arg2] + assert repr(instance) == "Timestamp.timestamp_extract(Part)" + infix_instance = arg1.timestamp_extract(arg2) + assert infix_instance == instance + + instance_tz = Expression.timestamp_extract(arg1, arg2, arg3) + assert instance_tz.name == "timestamp_extract" + assert instance_tz.params == [arg1, arg2, arg3] + assert repr(instance_tz) == "Timestamp.timestamp_extract(Part, Timezone)" + infix_instance_tz = arg1.timestamp_extract(arg2, arg3) + assert infix_instance_tz == instance_tz + + def test_timestamp_diff(self): + arg1 = self._make_arg("End") + arg2 = self._make_arg("Start") + arg3 = self._make_arg("Unit") + instance = Expression.timestamp_diff(arg1, arg2, arg3) + assert instance.name == "timestamp_diff" + assert instance.params == [arg1, arg2, arg3] + assert repr(instance) == "End.timestamp_diff(Start, Unit)" + infix_instance = arg1.timestamp_diff(arg2, arg3) + assert infix_instance == instance + + def test_if_null(self): + arg1 = self._make_arg("Field1") + arg2 = self._make_arg("Field2") + arg3 = self._make_arg("Field3") + instance = Expression.if_null(arg1, arg2, arg3) + assert instance.name == "if_null" + assert instance.params == [arg1, arg2, arg3] + assert repr(instance) == "Field1.if_null(Field2, Field3)" + infix_instance = arg1.if_null(arg2, arg3) + assert infix_instance == instance + + def test_type(self): + arg1 = self._make_arg("Value") + instance = Expression.type(arg1) + assert instance.name == "type" + assert instance.params == [arg1] + assert repr(instance) == "Value.type()" + infix_instance = arg1.type() + assert infix_instance == instance + + def test_is_type(self): + arg1 = self._make_arg("Value") + arg2 = self._make_arg("TypeString") + instance = Expression.is_type(arg1, arg2) + assert instance.name == "is_type" + assert instance.params == [arg1, arg2] + assert repr(instance) == "Value.is_type(TypeString)" + infix_instance = arg1.is_type(arg2) + assert infix_instance == instance + + def test_type_enum(self): + from google.cloud.firestore_v1.pipeline_expressions import Type + + arg1 = self._make_arg("Value") + instance = Expression.is_type(arg1, Type.STRING) + assert instance.name == "is_type" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == Type.STRING.value + assert repr(instance) == "Value.is_type(Constant.of('string'))" + infix_instance = arg1.is_type(Type.STRING) + assert infix_instance == instance + + def test_timestamp_enums(self): + from google.cloud.firestore_v1.pipeline_expressions import ( + TimeGranularity, + TimePart, + TimeUnit, + ) + + arg1 = self._make_arg("Value") + + instance_add = Expression.timestamp_add(arg1, TimeUnit.DAY, 1) + assert instance_add.name == "timestamp_add" + assert isinstance(instance_add.params[1], Constant) + assert instance_add.params[1].value == TimeUnit.DAY.value + + instance_trunc = Expression.timestamp_trunc(arg1, TimeGranularity.MONTH) + assert instance_trunc.name == "timestamp_trunc" + assert isinstance(instance_trunc.params[1], Constant) + assert instance_trunc.params[1].value == TimeGranularity.MONTH.value + + instance_trunc_inherited = Expression.timestamp_trunc( + arg1, TimeGranularity.MICROSECOND + ) + assert instance_trunc_inherited.name == "timestamp_trunc" + assert isinstance(instance_trunc_inherited.params[1], Constant) + assert ( + instance_trunc_inherited.params[1].value + == TimeGranularity.MICROSECOND.value + ) + + instance_extract = Expression.timestamp_extract(arg1, TimePart.YEAR) + assert instance_extract.name == "timestamp_extract" + assert isinstance(instance_extract.params[1], Constant) + assert instance_extract.params[1].value == TimePart.YEAR.value + + instance_extract_inherited = Expression.timestamp_extract(arg1, TimePart.SECOND) + assert instance_extract_inherited.name == "timestamp_extract" + assert isinstance(instance_extract_inherited.params[1], Constant) + assert instance_extract_inherited.params[1].value == TimePart.SECOND.value + def test_array_first(self): arg1 = self._make_arg("Value") instance = Expression.array_first(arg1)