|
30 | 30 | Union[str, int, float, decimal.Decimal, bool, datetime.datetime, datetime.date]
|
31 | 31 | ]
|
32 | 32 |
|
| 33 | +_RANGE_ELEMENT_TYPE_STR = {"TIMESTAMP", "DATETIME", "DATE"} |
| 34 | + |
33 | 35 |
|
34 | 36 | class ConnectionProperty:
|
35 | 37 | """A connection-level property to customize query behavior.
|
@@ -362,6 +364,129 @@ def __repr__(self):
|
362 | 364 | return f"{self.__class__.__name__}({items}{name}{description})"
|
363 | 365 |
|
364 | 366 |
|
| 367 | +class RangeQueryParameterType(_AbstractQueryParameterType): |
| 368 | + """Type representation for range query parameters. |
| 369 | +
|
| 370 | + Args: |
| 371 | + type_ (Union[ScalarQueryParameterType, str]): |
| 372 | + Type of range element, must be one of 'TIMESTAMP', 'DATETIME', or |
| 373 | + 'DATE'. |
| 374 | + name (Optional[str]): |
| 375 | + The name of the query parameter. Primarily used if the type is |
| 376 | + one of the subfields in ``StructQueryParameterType`` instance. |
| 377 | + description (Optional[str]): |
| 378 | + The query parameter description. Primarily used if the type is |
| 379 | + one of the subfields in ``StructQueryParameterType`` instance. |
| 380 | + """ |
| 381 | + |
| 382 | + @classmethod |
| 383 | + def _parse_range_element_type(self, type_): |
| 384 | + """Helper method that parses the input range element type, which may |
| 385 | + be a string, or a ScalarQueryParameterType object. |
| 386 | +
|
| 387 | + Returns: |
| 388 | + google.cloud.bigquery.query.ScalarQueryParameterType: Instance |
| 389 | + """ |
| 390 | + if isinstance(type_, str): |
| 391 | + if type_ not in _RANGE_ELEMENT_TYPE_STR: |
| 392 | + raise ValueError( |
| 393 | + "If given as a string, range element type must be one of " |
| 394 | + "'TIMESTAMP', 'DATE', or 'DATETIME'." |
| 395 | + ) |
| 396 | + return ScalarQueryParameterType(type_) |
| 397 | + elif isinstance(type_, ScalarQueryParameterType): |
| 398 | + if type_._type not in _RANGE_ELEMENT_TYPE_STR: |
| 399 | + raise ValueError( |
| 400 | + "If given as a ScalarQueryParameter object, range element " |
| 401 | + "type must be one of 'TIMESTAMP', 'DATE', or 'DATETIME' " |
| 402 | + "type." |
| 403 | + ) |
| 404 | + return type_ |
| 405 | + else: |
| 406 | + raise ValueError( |
| 407 | + "range_type must be a string or ScalarQueryParameter object, " |
| 408 | + "of 'TIMESTAMP', 'DATE', or 'DATETIME' type." |
| 409 | + ) |
| 410 | + |
| 411 | + def __init__(self, type_, *, name=None, description=None): |
| 412 | + self.type_ = self._parse_range_element_type(type_) |
| 413 | + self.name = name |
| 414 | + self.description = description |
| 415 | + |
| 416 | + @classmethod |
| 417 | + def from_api_repr(cls, resource): |
| 418 | + """Factory: construct parameter type from JSON resource. |
| 419 | +
|
| 420 | + Args: |
| 421 | + resource (Dict): JSON mapping of parameter |
| 422 | +
|
| 423 | + Returns: |
| 424 | + google.cloud.bigquery.query.RangeQueryParameterType: Instance |
| 425 | + """ |
| 426 | + type_ = resource["rangeElementType"]["type"] |
| 427 | + name = resource.get("name") |
| 428 | + description = resource.get("description") |
| 429 | + |
| 430 | + return cls(type_, name=name, description=description) |
| 431 | + |
| 432 | + def to_api_repr(self): |
| 433 | + """Construct JSON API representation for the parameter type. |
| 434 | +
|
| 435 | + Returns: |
| 436 | + Dict: JSON mapping |
| 437 | + """ |
| 438 | + # Name and description are only used if the type is a field inside a struct |
| 439 | + # type, but it's StructQueryParameterType's responsibilty to use these two |
| 440 | + # attributes in the API representation when needed. Here we omit them. |
| 441 | + return { |
| 442 | + "type": "RANGE", |
| 443 | + "rangeElementType": self.type_.to_api_repr(), |
| 444 | + } |
| 445 | + |
| 446 | + def with_name(self, new_name: Union[str, None]): |
| 447 | + """Return a copy of the instance with ``name`` set to ``new_name``. |
| 448 | +
|
| 449 | + Args: |
| 450 | + name (Union[str, None]): |
| 451 | + The new name of the range query parameter type. If ``None``, |
| 452 | + the existing name is cleared. |
| 453 | +
|
| 454 | + Returns: |
| 455 | + google.cloud.bigquery.query.RangeQueryParameterType: |
| 456 | + A new instance with updated name. |
| 457 | + """ |
| 458 | + return type(self)(self.type_, name=new_name, description=self.description) |
| 459 | + |
| 460 | + def __repr__(self): |
| 461 | + name = f", name={self.name!r}" if self.name is not None else "" |
| 462 | + description = ( |
| 463 | + f", description={self.description!r}" |
| 464 | + if self.description is not None |
| 465 | + else "" |
| 466 | + ) |
| 467 | + return f"{self.__class__.__name__}({self.type_!r}{name}{description})" |
| 468 | + |
| 469 | + def _key(self): |
| 470 | + """A tuple key that uniquely describes this field. |
| 471 | +
|
| 472 | + Used to compute this instance's hashcode and evaluate equality. |
| 473 | +
|
| 474 | + Returns: |
| 475 | + Tuple: The contents of this |
| 476 | + :class:`~google.cloud.bigquery.query.RangeQueryParameterType`. |
| 477 | + """ |
| 478 | + type_ = self.type_.to_api_repr() |
| 479 | + return (self.name, type_, self.description) |
| 480 | + |
| 481 | + def __eq__(self, other): |
| 482 | + if not isinstance(other, RangeQueryParameterType): |
| 483 | + return NotImplemented |
| 484 | + return self._key() == other._key() |
| 485 | + |
| 486 | + def __ne__(self, other): |
| 487 | + return not self == other |
| 488 | + |
| 489 | + |
365 | 490 | class _AbstractQueryParameter(object):
|
366 | 491 | """Base class for named / positional query parameters."""
|
367 | 492 |
|
@@ -811,6 +936,178 @@ def __repr__(self):
|
811 | 936 | return "StructQueryParameter{}".format(self._key())
|
812 | 937 |
|
813 | 938 |
|
| 939 | +class RangeQueryParameter(_AbstractQueryParameter): |
| 940 | + """Named / positional query parameters for range values. |
| 941 | +
|
| 942 | + Args: |
| 943 | + range_element_type (Union[str, RangeQueryParameterType]): |
| 944 | + The type of range elements. It must be one of 'TIMESTAMP', |
| 945 | + 'DATE', or 'DATETIME'. |
| 946 | +
|
| 947 | + start (Optional[Union[ScalarQueryParameter, str]]): |
| 948 | + The start of the range value. Must be the same type as |
| 949 | + range_element_type. If not provided, it's interpreted as UNBOUNDED. |
| 950 | +
|
| 951 | + end (Optional[Union[ScalarQueryParameter, str]]): |
| 952 | + The end of the range value. Must be the same type as |
| 953 | + range_element_type. If not provided, it's interpreted as UNBOUNDED. |
| 954 | +
|
| 955 | + name (Optional[str]): |
| 956 | + Parameter name, used via ``@foo`` syntax. If None, the |
| 957 | + parameter can only be addressed via position (``?``). |
| 958 | + """ |
| 959 | + |
| 960 | + @classmethod |
| 961 | + def _parse_range_element_type(self, range_element_type): |
| 962 | + if isinstance(range_element_type, str): |
| 963 | + if range_element_type not in _RANGE_ELEMENT_TYPE_STR: |
| 964 | + raise ValueError( |
| 965 | + "If given as a string, range_element_type must be one of " |
| 966 | + f"'TIMESTAMP', 'DATE', or 'DATETIME'. Got {range_element_type}." |
| 967 | + ) |
| 968 | + return RangeQueryParameterType(range_element_type) |
| 969 | + elif isinstance(range_element_type, RangeQueryParameterType): |
| 970 | + if range_element_type.type_._type not in _RANGE_ELEMENT_TYPE_STR: |
| 971 | + raise ValueError( |
| 972 | + "If given as a RangeQueryParameterType object, " |
| 973 | + "range_element_type must be one of 'TIMESTAMP', 'DATE', " |
| 974 | + "or 'DATETIME' type." |
| 975 | + ) |
| 976 | + return range_element_type |
| 977 | + else: |
| 978 | + raise ValueError( |
| 979 | + "range_element_type must be a string or " |
| 980 | + "RangeQueryParameterType object, of 'TIMESTAMP', 'DATE', " |
| 981 | + "or 'DATETIME' type. Got " |
| 982 | + f"{type(range_element_type)}:{range_element_type}" |
| 983 | + ) |
| 984 | + |
| 985 | + @classmethod |
| 986 | + def _serialize_range_element_value(self, value, type_): |
| 987 | + if value is None or isinstance(value, str): |
| 988 | + return value |
| 989 | + else: |
| 990 | + converter = _SCALAR_VALUE_TO_JSON_PARAM.get(type_) |
| 991 | + if converter is not None: |
| 992 | + return converter(value) # type: ignore |
| 993 | + else: |
| 994 | + raise ValueError( |
| 995 | + f"Cannot convert range element value from type {type_}, " |
| 996 | + "must be one of the strings 'TIMESTAMP', 'DATE' " |
| 997 | + "'DATETIME' or a RangeQueryParameterType object." |
| 998 | + ) |
| 999 | + |
| 1000 | + def __init__( |
| 1001 | + self, |
| 1002 | + range_element_type, |
| 1003 | + start=None, |
| 1004 | + end=None, |
| 1005 | + name=None, |
| 1006 | + ): |
| 1007 | + self.name = name |
| 1008 | + self.range_element_type = self._parse_range_element_type(range_element_type) |
| 1009 | + print(self.range_element_type.type_._type) |
| 1010 | + self.start = start |
| 1011 | + self.end = end |
| 1012 | + |
| 1013 | + @classmethod |
| 1014 | + def positional( |
| 1015 | + cls, range_element_type, start=None, end=None |
| 1016 | + ) -> "RangeQueryParameter": |
| 1017 | + """Factory for positional parameters. |
| 1018 | +
|
| 1019 | + Args: |
| 1020 | + range_element_type (Union[str, RangeQueryParameterType]): |
| 1021 | + The type of range elements. It must be one of `'TIMESTAMP'`, |
| 1022 | + `'DATE'`, or `'DATETIME'`. |
| 1023 | +
|
| 1024 | + start (Optional[Union[ScalarQueryParameter, str]]): |
| 1025 | + The start of the range value. Must be the same type as |
| 1026 | + range_element_type. If not provided, it's interpreted as |
| 1027 | + UNBOUNDED. |
| 1028 | +
|
| 1029 | + end (Optional[Union[ScalarQueryParameter, str]]): |
| 1030 | + The end of the range value. Must be the same type as |
| 1031 | + range_element_type. If not provided, it's interpreted as |
| 1032 | + UNBOUNDED. |
| 1033 | +
|
| 1034 | + Returns: |
| 1035 | + google.cloud.bigquery.query.RangeQueryParameter: Instance without |
| 1036 | + name. |
| 1037 | + """ |
| 1038 | + return cls(range_element_type, start, end) |
| 1039 | + |
| 1040 | + @classmethod |
| 1041 | + def from_api_repr(cls, resource: dict) -> "RangeQueryParameter": |
| 1042 | + """Factory: construct parameter from JSON resource. |
| 1043 | +
|
| 1044 | + Args: |
| 1045 | + resource (Dict): JSON mapping of parameter |
| 1046 | +
|
| 1047 | + Returns: |
| 1048 | + google.cloud.bigquery.query.RangeQueryParameter: Instance |
| 1049 | + """ |
| 1050 | + name = resource.get("name") |
| 1051 | + range_element_type = ( |
| 1052 | + resource.get("parameterType", {}).get("rangeElementType", {}).get("type") |
| 1053 | + ) |
| 1054 | + range_value = resource.get("parameterValue", {}).get("rangeValue", {}) |
| 1055 | + start = range_value.get("start", {}).get("value") |
| 1056 | + end = range_value.get("end", {}).get("value") |
| 1057 | + |
| 1058 | + return cls(range_element_type, start=start, end=end, name=name) |
| 1059 | + |
| 1060 | + def to_api_repr(self) -> dict: |
| 1061 | + """Construct JSON API representation for the parameter. |
| 1062 | +
|
| 1063 | + Returns: |
| 1064 | + Dict: JSON mapping |
| 1065 | + """ |
| 1066 | + range_element_type = self.range_element_type.to_api_repr() |
| 1067 | + type_ = self.range_element_type.type_._type |
| 1068 | + start = self._serialize_range_element_value(self.start, type_) |
| 1069 | + end = self._serialize_range_element_value(self.end, type_) |
| 1070 | + resource = { |
| 1071 | + "parameterType": range_element_type, |
| 1072 | + "parameterValue": { |
| 1073 | + "rangeValue": { |
| 1074 | + "start": {"value": start}, |
| 1075 | + "end": {"value": end}, |
| 1076 | + }, |
| 1077 | + }, |
| 1078 | + } |
| 1079 | + |
| 1080 | + # distinguish between name not provided vs. name being empty string |
| 1081 | + if self.name is not None: |
| 1082 | + resource["name"] = self.name |
| 1083 | + |
| 1084 | + return resource |
| 1085 | + |
| 1086 | + def _key(self): |
| 1087 | + """A tuple key that uniquely describes this field. |
| 1088 | +
|
| 1089 | + Used to compute this instance's hashcode and evaluate equality. |
| 1090 | +
|
| 1091 | + Returns: |
| 1092 | + Tuple: The contents of this |
| 1093 | + :class:`~google.cloud.bigquery.query.RangeQueryParameter`. |
| 1094 | + """ |
| 1095 | + |
| 1096 | + range_element_type = self.range_element_type.to_api_repr() |
| 1097 | + return (self.name, range_element_type, self.start, self.end) |
| 1098 | + |
| 1099 | + def __eq__(self, other): |
| 1100 | + if not isinstance(other, RangeQueryParameter): |
| 1101 | + return NotImplemented |
| 1102 | + return self._key() == other._key() |
| 1103 | + |
| 1104 | + def __ne__(self, other): |
| 1105 | + return not self == other |
| 1106 | + |
| 1107 | + def __repr__(self): |
| 1108 | + return "RangeQueryParameter{}".format(self._key()) |
| 1109 | + |
| 1110 | + |
814 | 1111 | class SqlParameterScalarTypes:
|
815 | 1112 | """Supported scalar SQL query parameter types as type objects."""
|
816 | 1113 |
|
|
0 commit comments