Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb359a9a

Browse files
authored
feat: support RANGE query parameters (#1827)
* feat: RANGE query parameters and unit tests* unit test* unit test coverage* lint* lint* lint* system test* fix system test* ajust init items order* fix typos and improve docstrings
1 parente81a13c commitb359a9a

File tree

5 files changed

+883
-1
lines changed

5 files changed

+883
-1
lines changed

‎benchmark/benchmark.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def _is_datetime_min(time_str: str) -> bool:
231231

232232

233233
def_summary(run:dict)->str:
234-
"""Coverts run dict to run summary string."""
234+
"""Converts run dict to run summary string."""
235235
no_val="NODATA"
236236
output= ["QUERYTIME "]
237237

‎google/cloud/bigquery/__init__.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
fromgoogle.cloud.bigquery.queryimportConnectionProperty
8484
fromgoogle.cloud.bigquery.queryimportScalarQueryParameter
8585
fromgoogle.cloud.bigquery.queryimportScalarQueryParameterType
86+
fromgoogle.cloud.bigquery.queryimportRangeQueryParameter
87+
fromgoogle.cloud.bigquery.queryimportRangeQueryParameterType
8688
fromgoogle.cloud.bigquery.queryimportSqlParameterScalarTypes
8789
fromgoogle.cloud.bigquery.queryimportStructQueryParameter
8890
fromgoogle.cloud.bigquery.queryimportStructQueryParameterType
@@ -122,10 +124,12 @@
122124
"ArrayQueryParameter",
123125
"ScalarQueryParameter",
124126
"StructQueryParameter",
127+
"RangeQueryParameter",
125128
"ArrayQueryParameterType",
126129
"ScalarQueryParameterType",
127130
"SqlParameterScalarTypes",
128131
"StructQueryParameterType",
132+
"RangeQueryParameterType",
129133
# Datasets
130134
"Dataset",
131135
"DatasetReference",

‎google/cloud/bigquery/query.py‎

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
Union[str,int,float,decimal.Decimal,bool,datetime.datetime,datetime.date]
3131
]
3232

33+
_RANGE_ELEMENT_TYPE_STR= {"TIMESTAMP","DATETIME","DATE"}
34+
3335

3436
classConnectionProperty:
3537
"""A connection-level property to customize query behavior.
@@ -362,6 +364,129 @@ def __repr__(self):
362364
returnf"{self.__class__.__name__}({items}{name}{description})"
363365

364366

367+
classRangeQueryParameterType(_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+
ifisinstance(type_,str):
391+
iftype_notin_RANGE_ELEMENT_TYPE_STR:
392+
raiseValueError(
393+
"If given as a string, range element type must be one of "
394+
"'TIMESTAMP', 'DATE', or 'DATETIME'."
395+
)
396+
returnScalarQueryParameterType(type_)
397+
elifisinstance(type_,ScalarQueryParameterType):
398+
iftype_._typenotin_RANGE_ELEMENT_TYPE_STR:
399+
raiseValueError(
400+
"If given as a ScalarQueryParameter object, range element "
401+
"type must be one of 'TIMESTAMP', 'DATE', or 'DATETIME' "
402+
"type."
403+
)
404+
returntype_
405+
else:
406+
raiseValueError(
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+
deffrom_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+
returncls(type_,name=name,description=description)
431+
432+
defto_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+
defwith_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+
returntype(self)(self.type_,name=new_name,description=self.description)
459+
460+
def__repr__(self):
461+
name=f", name={self.name!r}"ifself.nameisnotNoneelse""
462+
description= (
463+
f", description={self.description!r}"
464+
ifself.descriptionisnotNone
465+
else""
466+
)
467+
returnf"{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+
ifnotisinstance(other,RangeQueryParameterType):
483+
returnNotImplemented
484+
returnself._key()==other._key()
485+
486+
def__ne__(self,other):
487+
returnnotself==other
488+
489+
365490
class_AbstractQueryParameter(object):
366491
"""Base class for named / positional query parameters."""
367492

@@ -811,6 +936,178 @@ def __repr__(self):
811936
return"StructQueryParameter{}".format(self._key())
812937

813938

939+
classRangeQueryParameter(_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+
ifisinstance(range_element_type,str):
963+
ifrange_element_typenotin_RANGE_ELEMENT_TYPE_STR:
964+
raiseValueError(
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+
returnRangeQueryParameterType(range_element_type)
969+
elifisinstance(range_element_type,RangeQueryParameterType):
970+
ifrange_element_type.type_._typenotin_RANGE_ELEMENT_TYPE_STR:
971+
raiseValueError(
972+
"If given as a RangeQueryParameterType object, "
973+
"range_element_type must be one of 'TIMESTAMP', 'DATE', "
974+
"or 'DATETIME' type."
975+
)
976+
returnrange_element_type
977+
else:
978+
raiseValueError(
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+
ifvalueisNoneorisinstance(value,str):
988+
returnvalue
989+
else:
990+
converter=_SCALAR_VALUE_TO_JSON_PARAM.get(type_)
991+
ifconverterisnotNone:
992+
returnconverter(value)# type: ignore
993+
else:
994+
raiseValueError(
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+
defpositional(
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+
returncls(range_element_type,start,end)
1039+
1040+
@classmethod
1041+
deffrom_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+
returncls(range_element_type,start=start,end=end,name=name)
1059+
1060+
defto_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+
ifself.nameisnotNone:
1082+
resource["name"]=self.name
1083+
1084+
returnresource
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+
ifnotisinstance(other,RangeQueryParameter):
1101+
returnNotImplemented
1102+
returnself._key()==other._key()
1103+
1104+
def__ne__(self,other):
1105+
returnnotself==other
1106+
1107+
def__repr__(self):
1108+
return"RangeQueryParameter{}".format(self._key())
1109+
1110+
8141111
classSqlParameterScalarTypes:
8151112
"""Supported scalar SQL query parameter types as type objects."""
8161113

‎tests/system/test_query.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
fromgoogle.cloud.bigquery.queryimportScalarQueryParameterType
2727
fromgoogle.cloud.bigquery.queryimportStructQueryParameter
2828
fromgoogle.cloud.bigquery.queryimportStructQueryParameterType
29+
fromgoogle.cloud.bigquery.queryimportRangeQueryParameter
2930

3031

3132
@pytest.fixture(params=["INSERT","QUERY"])
@@ -422,6 +423,38 @@ def test_query_statistics(bigquery_client, query_api_method):
422423
)
423424
],
424425
),
426+
(
427+
"SELECT @range_date",
428+
"[2016-12-05, UNBOUNDED)",
429+
[
430+
RangeQueryParameter(
431+
name="range_date",
432+
range_element_type="DATE",
433+
start=datetime.date(2016,12,5),
434+
)
435+
],
436+
),
437+
(
438+
"SELECT @range_datetime",
439+
"[2016-12-05T00:00:00, UNBOUNDED)",
440+
[
441+
RangeQueryParameter(
442+
name="range_datetime",
443+
range_element_type="DATETIME",
444+
start=datetime.datetime(2016,12,5),
445+
)
446+
],
447+
),
448+
(
449+
"SELECT @range_unbounded",
450+
"[UNBOUNDED, UNBOUNDED)",
451+
[
452+
RangeQueryParameter(
453+
name="range_unbounded",
454+
range_element_type="DATETIME",
455+
)
456+
],
457+
),
425458
),
426459
)
427460
deftest_query_parameters(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp