Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/cloud/bigquery/query.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

483 statements  

1# Copyright 2015 Google LLC 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""BigQuery query processing.""" 

16 

17from collections import OrderedDict 

18import copy 

19import datetime 

20import decimal 

21from typing import Any, cast, Optional, Dict, Union 

22 

23from google.cloud.bigquery.table import _parse_schema_resource 

24from google.cloud.bigquery import _helpers 

25from google.cloud.bigquery._helpers import _rows_from_json 

26from google.cloud.bigquery._helpers import _SCALAR_VALUE_TO_JSON_PARAM 

27from google.cloud.bigquery._helpers import _SUPPORTED_RANGE_ELEMENTS 

28 

29 

30_SCALAR_VALUE_TYPE = Optional[ 

31 Union[str, int, float, decimal.Decimal, bool, datetime.datetime, datetime.date] 

32] 

33 

34 

35class ConnectionProperty: 

36 """A connection-level property to customize query behavior. 

37 

38 See 

39 https://cloud.google.com/bigquery/docs/reference/rest/v2/ConnectionProperty 

40 

41 Args: 

42 key: 

43 The key of the property to set, for example, ``'time_zone'`` or 

44 ``'session_id'``. 

45 value: The value of the property to set. 

46 """ 

47 

48 def __init__(self, key: str = "", value: str = ""): 

49 self._properties = { 

50 "key": key, 

51 "value": value, 

52 } 

53 

54 @property 

55 def key(self) -> str: 

56 """Name of the property. 

57 

58 For example: 

59 

60 * ``time_zone`` 

61 * ``session_id`` 

62 """ 

63 return self._properties["key"] 

64 

65 @property 

66 def value(self) -> str: 

67 """Value of the property.""" 

68 return self._properties["value"] 

69 

70 @classmethod 

71 def from_api_repr(cls, resource) -> "ConnectionProperty": 

72 """Construct :class:`~google.cloud.bigquery.query.ConnectionProperty` 

73 from JSON resource. 

74 

75 Args: 

76 resource: JSON representation. 

77 

78 Returns: 

79 A connection property. 

80 """ 

81 value = cls() 

82 value._properties = resource 

83 return value 

84 

85 def to_api_repr(self) -> Dict[str, Any]: 

86 """Construct JSON API representation for the connection property. 

87 

88 Returns: 

89 JSON mapping 

90 """ 

91 return self._properties 

92 

93 

94class UDFResource(object): 

95 """Describe a single user-defined function (UDF) resource. 

96 

97 Args: 

98 udf_type (str): The type of the resource ('inlineCode' or 'resourceUri') 

99 

100 value (str): The inline code or resource URI. 

101 

102 See: 

103 https://cloud.google.com/bigquery/user-defined-functions#api 

104 """ 

105 

106 def __init__(self, udf_type, value): 

107 self.udf_type = udf_type 

108 self.value = value 

109 

110 def __eq__(self, other): 

111 if not isinstance(other, UDFResource): 

112 return NotImplemented 

113 return self.udf_type == other.udf_type and self.value == other.value 

114 

115 def __ne__(self, other): 

116 return not self == other 

117 

118 

119class _AbstractQueryParameterType: 

120 """Base class for representing query parameter types. 

121 

122 https://cloud.google.com/bigquery/docs/reference/rest/v2/QueryParameter#queryparametertype 

123 """ 

124 

125 @classmethod 

126 def from_api_repr(cls, resource): 

127 """Factory: construct parameter type from JSON resource. 

128 

129 Args: 

130 resource (Dict): JSON mapping of parameter 

131 

132 Returns: 

133 google.cloud.bigquery.query.QueryParameterType: Instance 

134 """ 

135 raise NotImplementedError 

136 

137 def to_api_repr(self): 

138 """Construct JSON API representation for the parameter type. 

139 

140 Returns: 

141 Dict: JSON mapping 

142 """ 

143 raise NotImplementedError 

144 

145 

146class ScalarQueryParameterType(_AbstractQueryParameterType): 

147 """Type representation for scalar query parameters. 

148 

149 Args: 

150 type_ (str): 

151 One of 'STRING', 'INT64', 'FLOAT64', 'NUMERIC', 'BOOL', 'TIMESTAMP', 

152 'DATETIME', or 'DATE'. 

153 name (Optional[str]): 

154 The name of the query parameter. Primarily used if the type is 

155 one of the subfields in ``StructQueryParameterType`` instance. 

156 description (Optional[str]): 

157 The query parameter description. Primarily used if the type is 

158 one of the subfields in ``StructQueryParameterType`` instance. 

159 """ 

160 

161 def __init__(self, type_, *, name=None, description=None): 

162 self._type = type_ 

163 self.name = name 

164 self.description = description 

165 

166 @classmethod 

167 def from_api_repr(cls, resource): 

168 """Factory: construct parameter type from JSON resource. 

169 

170 Args: 

171 resource (Dict): JSON mapping of parameter 

172 

173 Returns: 

174 google.cloud.bigquery.query.ScalarQueryParameterType: Instance 

175 """ 

176 type_ = resource["type"] 

177 return cls(type_) 

178 

179 def to_api_repr(self): 

180 """Construct JSON API representation for the parameter type. 

181 

182 Returns: 

183 Dict: JSON mapping 

184 """ 

185 # Name and description are only used if the type is a field inside a struct 

186 # type, but it's StructQueryParameterType's responsibilty to use these two 

187 # attributes in the API representation when needed. Here we omit them. 

188 return {"type": self._type} 

189 

190 def with_name(self, new_name: Union[str, None]): 

191 """Return a copy of the instance with ``name`` set to ``new_name``. 

192 

193 Args: 

194 name (Union[str, None]): 

195 The new name of the query parameter type. If ``None``, the existing 

196 name is cleared. 

197 

198 Returns: 

199 google.cloud.bigquery.query.ScalarQueryParameterType: 

200 A new instance with updated name. 

201 """ 

202 return type(self)(self._type, name=new_name, description=self.description) 

203 

204 def __repr__(self): 

205 name = f", name={self.name!r}" if self.name is not None else "" 

206 description = ( 

207 f", description={self.description!r}" 

208 if self.description is not None 

209 else "" 

210 ) 

211 return f"{self.__class__.__name__}({self._type!r}{name}{description})" 

212 

213 

214class ArrayQueryParameterType(_AbstractQueryParameterType): 

215 """Type representation for array query parameters. 

216 

217 Args: 

218 array_type (Union[ScalarQueryParameterType, StructQueryParameterType]): 

219 The type of array elements. 

220 name (Optional[str]): 

221 The name of the query parameter. Primarily used if the type is 

222 one of the subfields in ``StructQueryParameterType`` instance. 

223 description (Optional[str]): 

224 The query parameter description. Primarily used if the type is 

225 one of the subfields in ``StructQueryParameterType`` instance. 

226 """ 

227 

228 def __init__(self, array_type, *, name=None, description=None): 

229 self._array_type = array_type 

230 self.name = name 

231 self.description = description 

232 

233 @classmethod 

234 def from_api_repr(cls, resource): 

235 """Factory: construct parameter type from JSON resource. 

236 

237 Args: 

238 resource (Dict): JSON mapping of parameter 

239 

240 Returns: 

241 google.cloud.bigquery.query.ArrayQueryParameterType: Instance 

242 """ 

243 array_item_type = resource["arrayType"]["type"] 

244 

245 if array_item_type in {"STRUCT", "RECORD"}: 

246 klass = StructQueryParameterType 

247 else: 

248 klass = ScalarQueryParameterType 

249 

250 item_type_instance = klass.from_api_repr(resource["arrayType"]) 

251 return cls(item_type_instance) 

252 

253 def to_api_repr(self): 

254 """Construct JSON API representation for the parameter type. 

255 

256 Returns: 

257 Dict: JSON mapping 

258 """ 

259 # Name and description are only used if the type is a field inside a struct 

260 # type, but it's StructQueryParameterType's responsibilty to use these two 

261 # attributes in the API representation when needed. Here we omit them. 

262 return { 

263 "type": "ARRAY", 

264 "arrayType": self._array_type.to_api_repr(), 

265 } 

266 

267 def __repr__(self): 

268 name = f", name={self.name!r}" if self.name is not None else "" 

269 description = ( 

270 f", description={self.description!r}" 

271 if self.description is not None 

272 else "" 

273 ) 

274 return f"{self.__class__.__name__}({self._array_type!r}{name}{description})" 

275 

276 

277class StructQueryParameterType(_AbstractQueryParameterType): 

278 """Type representation for struct query parameters. 

279 

280 Args: 

281 fields (Iterable[Union[ \ 

282 ArrayQueryParameterType, ScalarQueryParameterType, StructQueryParameterType \ 

283 ]]): 

284 An non-empty iterable describing the struct's field types. 

285 name (Optional[str]): 

286 The name of the query parameter. Primarily used if the type is 

287 one of the subfields in ``StructQueryParameterType`` instance. 

288 description (Optional[str]): 

289 The query parameter description. Primarily used if the type is 

290 one of the subfields in ``StructQueryParameterType`` instance. 

291 """ 

292 

293 def __init__(self, *fields, name=None, description=None): 

294 if not fields: 

295 raise ValueError("Struct type must have at least one field defined.") 

296 

297 self._fields = fields # fields is a tuple (immutable), no shallow copy needed 

298 self.name = name 

299 self.description = description 

300 

301 @property 

302 def fields(self): 

303 return self._fields # no copy needed, self._fields is an immutable sequence 

304 

305 @classmethod 

306 def from_api_repr(cls, resource): 

307 """Factory: construct parameter type from JSON resource. 

308 

309 Args: 

310 resource (Dict): JSON mapping of parameter 

311 

312 Returns: 

313 google.cloud.bigquery.query.StructQueryParameterType: Instance 

314 """ 

315 fields = [] 

316 

317 for struct_field in resource["structTypes"]: 

318 type_repr = struct_field["type"] 

319 if type_repr["type"] in {"STRUCT", "RECORD"}: 

320 klass = StructQueryParameterType 

321 elif type_repr["type"] == "ARRAY": 

322 klass = ArrayQueryParameterType 

323 else: 

324 klass = ScalarQueryParameterType 

325 

326 type_instance = klass.from_api_repr(type_repr) 

327 type_instance.name = struct_field.get("name") 

328 type_instance.description = struct_field.get("description") 

329 fields.append(type_instance) 

330 

331 return cls(*fields) 

332 

333 def to_api_repr(self): 

334 """Construct JSON API representation for the parameter type. 

335 

336 Returns: 

337 Dict: JSON mapping 

338 """ 

339 fields = [] 

340 

341 for field in self._fields: 

342 item = {"type": field.to_api_repr()} 

343 if field.name is not None: 

344 item["name"] = field.name 

345 if field.description is not None: 

346 item["description"] = field.description 

347 

348 fields.append(item) 

349 

350 return { 

351 "type": "STRUCT", 

352 "structTypes": fields, 

353 } 

354 

355 def __repr__(self): 

356 name = f", name={self.name!r}" if self.name is not None else "" 

357 description = ( 

358 f", description={self.description!r}" 

359 if self.description is not None 

360 else "" 

361 ) 

362 items = ", ".join(repr(field) for field in self._fields) 

363 return f"{self.__class__.__name__}({items}{name}{description})" 

364 

365 

366class RangeQueryParameterType(_AbstractQueryParameterType): 

367 """Type representation for range query parameters. 

368 

369 Args: 

370 type_ (Union[ScalarQueryParameterType, str]): 

371 Type of range element, must be one of 'TIMESTAMP', 'DATETIME', or 

372 'DATE'. 

373 name (Optional[str]): 

374 The name of the query parameter. Primarily used if the type is 

375 one of the subfields in ``StructQueryParameterType`` instance. 

376 description (Optional[str]): 

377 The query parameter description. Primarily used if the type is 

378 one of the subfields in ``StructQueryParameterType`` instance. 

379 """ 

380 

381 @classmethod 

382 def _parse_range_element_type(self, type_): 

383 """Helper method that parses the input range element type, which may 

384 be a string, or a ScalarQueryParameterType object. 

385 

386 Returns: 

387 google.cloud.bigquery.query.ScalarQueryParameterType: Instance 

388 """ 

389 if isinstance(type_, str): 

390 if type_ not in _SUPPORTED_RANGE_ELEMENTS: 

391 raise ValueError( 

392 "If given as a string, range element type must be one of " 

393 "'TIMESTAMP', 'DATE', or 'DATETIME'." 

394 ) 

395 return ScalarQueryParameterType(type_) 

396 elif isinstance(type_, ScalarQueryParameterType): 

397 if type_._type not in _SUPPORTED_RANGE_ELEMENTS: 

398 raise ValueError( 

399 "If given as a ScalarQueryParameter object, range element " 

400 "type must be one of 'TIMESTAMP', 'DATE', or 'DATETIME' " 

401 "type." 

402 ) 

403 return type_ 

404 else: 

405 raise ValueError( 

406 "range_type must be a string or ScalarQueryParameter object, " 

407 "of 'TIMESTAMP', 'DATE', or 'DATETIME' type." 

408 ) 

409 

410 def __init__(self, type_, *, name=None, description=None): 

411 self.type_ = self._parse_range_element_type(type_) 

412 self.name = name 

413 self.description = description 

414 

415 @classmethod 

416 def from_api_repr(cls, resource): 

417 """Factory: construct parameter type from JSON resource. 

418 

419 Args: 

420 resource (Dict): JSON mapping of parameter 

421 

422 Returns: 

423 google.cloud.bigquery.query.RangeQueryParameterType: Instance 

424 """ 

425 type_ = resource["rangeElementType"]["type"] 

426 name = resource.get("name") 

427 description = resource.get("description") 

428 

429 return cls(type_, name=name, description=description) 

430 

431 def to_api_repr(self): 

432 """Construct JSON API representation for the parameter type. 

433 

434 Returns: 

435 Dict: JSON mapping 

436 """ 

437 # Name and description are only used if the type is a field inside a struct 

438 # type, but it's StructQueryParameterType's responsibilty to use these two 

439 # attributes in the API representation when needed. Here we omit them. 

440 return { 

441 "type": "RANGE", 

442 "rangeElementType": self.type_.to_api_repr(), 

443 } 

444 

445 def with_name(self, new_name: Union[str, None]): 

446 """Return a copy of the instance with ``name`` set to ``new_name``. 

447 

448 Args: 

449 name (Union[str, None]): 

450 The new name of the range query parameter type. If ``None``, 

451 the existing name is cleared. 

452 

453 Returns: 

454 google.cloud.bigquery.query.RangeQueryParameterType: 

455 A new instance with updated name. 

456 """ 

457 return type(self)(self.type_, name=new_name, description=self.description) 

458 

459 def __repr__(self): 

460 name = f", name={self.name!r}" if self.name is not None else "" 

461 description = ( 

462 f", description={self.description!r}" 

463 if self.description is not None 

464 else "" 

465 ) 

466 return f"{self.__class__.__name__}({self.type_!r}{name}{description})" 

467 

468 def _key(self): 

469 """A tuple key that uniquely describes this field. 

470 

471 Used to compute this instance's hashcode and evaluate equality. 

472 

473 Returns: 

474 Tuple: The contents of this 

475 :class:`~google.cloud.bigquery.query.RangeQueryParameterType`. 

476 """ 

477 type_ = self.type_.to_api_repr() 

478 return (self.name, type_, self.description) 

479 

480 def __eq__(self, other): 

481 if not isinstance(other, RangeQueryParameterType): 

482 return NotImplemented 

483 return self._key() == other._key() 

484 

485 def __ne__(self, other): 

486 return not self == other 

487 

488 

489class _AbstractQueryParameter(object): 

490 """Base class for named / positional query parameters.""" 

491 

492 @classmethod 

493 def from_api_repr(cls, resource: dict) -> "_AbstractQueryParameter": 

494 """Factory: construct parameter from JSON resource. 

495 

496 Args: 

497 resource (Dict): JSON mapping of parameter 

498 

499 Returns: 

500 A new instance of _AbstractQueryParameter subclass. 

501 """ 

502 raise NotImplementedError 

503 

504 def to_api_repr(self) -> dict: 

505 """Construct JSON API representation for the parameter. 

506 

507 Returns: 

508 Dict: JSON representation for the parameter. 

509 """ 

510 raise NotImplementedError 

511 

512 

513class ScalarQueryParameter(_AbstractQueryParameter): 

514 """Named / positional query parameters for scalar values. 

515 

516 Args: 

517 name: 

518 Parameter name, used via ``@foo`` syntax. If None, the 

519 parameter can only be addressed via position (``?``). 

520 

521 type_: 

522 Name of parameter type. See 

523 :class:`google.cloud.bigquery.enums.SqlTypeNames` and 

524 :class:`google.cloud.bigquery.query.SqlParameterScalarTypes` for 

525 supported types. 

526 

527 value: 

528 The scalar parameter value. 

529 """ 

530 

531 def __init__( 

532 self, 

533 name: Optional[str], 

534 type_: Optional[Union[str, ScalarQueryParameterType]], 

535 value: _SCALAR_VALUE_TYPE, 

536 ): 

537 self.name = name 

538 if isinstance(type_, ScalarQueryParameterType): 

539 self.type_ = type_._type 

540 else: 

541 self.type_ = type_ 

542 self.value = value 

543 

544 @classmethod 

545 def positional( 

546 cls, type_: Union[str, ScalarQueryParameterType], value: _SCALAR_VALUE_TYPE 

547 ) -> "ScalarQueryParameter": 

548 """Factory for positional paramater. 

549 

550 Args: 

551 type_: 

552 Name of parameter type. One of 'STRING', 'INT64', 

553 'FLOAT64', 'NUMERIC', 'BIGNUMERIC', 'BOOL', 'TIMESTAMP', 'DATETIME', or 

554 'DATE'. 

555 

556 value: 

557 The scalar parameter value. 

558 

559 Returns: 

560 google.cloud.bigquery.query.ScalarQueryParameter: Instance without name 

561 """ 

562 return cls(None, type_, value) 

563 

564 @classmethod 

565 def from_api_repr(cls, resource: dict) -> "ScalarQueryParameter": 

566 """Factory: construct parameter from JSON resource. 

567 

568 Args: 

569 resource (Dict): JSON mapping of parameter 

570 

571 Returns: 

572 google.cloud.bigquery.query.ScalarQueryParameter: Instance 

573 """ 

574 # Import here to avoid circular imports. 

575 from google.cloud.bigquery import schema 

576 

577 name = resource.get("name") 

578 type_ = resource["parameterType"]["type"] 

579 

580 # parameterValue might not be present if JSON resource originates 

581 # from the back-end - the latter omits it for None values. 

582 value = resource.get("parameterValue", {}).get("value") 

583 if value is not None: 

584 converted = _helpers.SCALAR_QUERY_PARAM_PARSER.to_py( 

585 value, schema.SchemaField(cast(str, name), type_) 

586 ) 

587 else: 

588 converted = None 

589 

590 return cls(name, type_, converted) 

591 

592 def to_api_repr(self) -> dict: 

593 """Construct JSON API representation for the parameter. 

594 

595 Returns: 

596 Dict: JSON mapping 

597 """ 

598 value = self.value 

599 converter = _SCALAR_VALUE_TO_JSON_PARAM.get(self.type_, lambda value: value) 

600 value = converter(value) # type: ignore 

601 resource: Dict[str, Any] = { 

602 "parameterType": {"type": self.type_}, 

603 "parameterValue": {"value": value}, 

604 } 

605 if self.name is not None: 

606 resource["name"] = self.name 

607 return resource 

608 

609 def _key(self): 

610 """A tuple key that uniquely describes this field. 

611 

612 Used to compute this instance's hashcode and evaluate equality. 

613 

614 Returns: 

615 Tuple: The contents of this :class:`~google.cloud.bigquery.query.ScalarQueryParameter`. 

616 """ 

617 return (self.name, self.type_.upper(), self.value) 

618 

619 def __eq__(self, other): 

620 if not isinstance(other, ScalarQueryParameter): 

621 return NotImplemented 

622 return self._key() == other._key() 

623 

624 def __ne__(self, other): 

625 return not self == other 

626 

627 def __repr__(self): 

628 return "ScalarQueryParameter{}".format(self._key()) 

629 

630 

631class ArrayQueryParameter(_AbstractQueryParameter): 

632 """Named / positional query parameters for array values. 

633 

634 Args: 

635 name (Optional[str]): 

636 Parameter name, used via ``@foo`` syntax. If None, the 

637 parameter can only be addressed via position (``?``). 

638 

639 array_type (Union[str, ScalarQueryParameterType, StructQueryParameterType]): 

640 The type of array elements. If given as a string, it must be one of 

641 `'STRING'`, `'INT64'`, `'FLOAT64'`, `'NUMERIC'`, `'BIGNUMERIC'`, `'BOOL'`, 

642 `'TIMESTAMP'`, `'DATE'`, or `'STRUCT'`/`'RECORD'`. 

643 If the type is ``'STRUCT'``/``'RECORD'`` and ``values`` is empty, 

644 the exact item type cannot be deduced, thus a ``StructQueryParameterType`` 

645 instance needs to be passed in. 

646 

647 values (List[appropriate type]): The parameter array values. 

648 """ 

649 

650 def __init__(self, name, array_type, values) -> None: 

651 self.name = name 

652 self.values = values 

653 

654 if isinstance(array_type, str): 

655 if not values and array_type in {"RECORD", "STRUCT"}: 

656 raise ValueError( 

657 "Missing detailed struct item type info for an empty array, " 

658 "please provide a StructQueryParameterType instance." 

659 ) 

660 self.array_type = array_type 

661 

662 @classmethod 

663 def positional(cls, array_type: str, values: list) -> "ArrayQueryParameter": 

664 """Factory for positional parameters. 

665 

666 Args: 

667 array_type (Union[str, ScalarQueryParameterType, StructQueryParameterType]): 

668 The type of array elements. If given as a string, it must be one of 

669 `'STRING'`, `'INT64'`, `'FLOAT64'`, `'NUMERIC'`, `'BIGNUMERIC'`, 

670 `'BOOL'`, `'TIMESTAMP'`, `'DATE'`, or `'STRUCT'`/`'RECORD'`. 

671 If the type is ``'STRUCT'``/``'RECORD'`` and ``values`` is empty, 

672 the exact item type cannot be deduced, thus a ``StructQueryParameterType`` 

673 instance needs to be passed in. 

674 

675 values (List[appropriate type]): The parameter array values. 

676 

677 Returns: 

678 google.cloud.bigquery.query.ArrayQueryParameter: Instance without name 

679 """ 

680 return cls(None, array_type, values) 

681 

682 @classmethod 

683 def _from_api_repr_struct(cls, resource): 

684 name = resource.get("name") 

685 converted = [] 

686 # We need to flatten the array to use the StructQueryParameter 

687 # parse code. 

688 resource_template = { 

689 # The arrayType includes all the types of the fields of the STRUCT 

690 "parameterType": resource["parameterType"]["arrayType"] 

691 } 

692 for array_value in resource["parameterValue"]["arrayValues"]: 

693 struct_resource = copy.deepcopy(resource_template) 

694 struct_resource["parameterValue"] = array_value 

695 struct_value = StructQueryParameter.from_api_repr(struct_resource) 

696 converted.append(struct_value) 

697 return cls(name, "STRUCT", converted) 

698 

699 @classmethod 

700 def _from_api_repr_scalar(cls, resource): 

701 """Converts REST resource into a list of scalar values.""" 

702 # Import here to avoid circular imports. 

703 from google.cloud.bigquery import schema 

704 

705 name = resource.get("name") 

706 array_type = resource["parameterType"]["arrayType"]["type"] 

707 parameter_value = resource.get("parameterValue", {}) 

708 array_values = parameter_value.get("arrayValues", ()) 

709 values = [value["value"] for value in array_values] 

710 converted = [ 

711 _helpers.SCALAR_QUERY_PARAM_PARSER.to_py( 

712 value, schema.SchemaField(name, array_type) 

713 ) 

714 for value in values 

715 ] 

716 return cls(name, array_type, converted) 

717 

718 @classmethod 

719 def from_api_repr(cls, resource: dict) -> "ArrayQueryParameter": 

720 """Factory: construct parameter from JSON resource. 

721 

722 Args: 

723 resource (Dict): JSON mapping of parameter 

724 

725 Returns: 

726 google.cloud.bigquery.query.ArrayQueryParameter: Instance 

727 """ 

728 array_type = resource["parameterType"]["arrayType"]["type"] 

729 if array_type == "STRUCT": 

730 return cls._from_api_repr_struct(resource) 

731 return cls._from_api_repr_scalar(resource) 

732 

733 def to_api_repr(self) -> dict: 

734 """Construct JSON API representation for the parameter. 

735 

736 Returns: 

737 Dict: JSON mapping 

738 """ 

739 values = self.values 

740 

741 if self.array_type in {"RECORD", "STRUCT"} or isinstance( 

742 self.array_type, StructQueryParameterType 

743 ): 

744 reprs = [value.to_api_repr() for value in values] 

745 a_values = [repr_["parameterValue"] for repr_ in reprs] 

746 

747 if reprs: 

748 a_type = reprs[0]["parameterType"] 

749 else: 

750 # This assertion always evaluates to True because the 

751 # constructor disallows STRUCT/RECORD type defined as a 

752 # string with empty values. 

753 assert isinstance(self.array_type, StructQueryParameterType) 

754 a_type = self.array_type.to_api_repr() 

755 else: 

756 # Scalar array item type. 

757 if isinstance(self.array_type, str): 

758 a_type = {"type": self.array_type} 

759 else: 

760 a_type = self.array_type.to_api_repr() 

761 

762 converter = _SCALAR_VALUE_TO_JSON_PARAM.get( 

763 a_type["type"], lambda value: value 

764 ) 

765 values = [converter(value) for value in values] # type: ignore 

766 a_values = [{"value": value} for value in values] 

767 

768 resource = { 

769 "parameterType": {"type": "ARRAY", "arrayType": a_type}, 

770 "parameterValue": {"arrayValues": a_values}, 

771 } 

772 if self.name is not None: 

773 resource["name"] = self.name 

774 

775 return resource 

776 

777 def _key(self): 

778 """A tuple key that uniquely describes this field. 

779 

780 Used to compute this instance's hashcode and evaluate equality. 

781 

782 Returns: 

783 Tuple: The contents of this :class:`~google.cloud.bigquery.query.ArrayQueryParameter`. 

784 """ 

785 if isinstance(self.array_type, str): 

786 item_type = self.array_type 

787 elif isinstance(self.array_type, ScalarQueryParameterType): 

788 item_type = self.array_type._type 

789 else: 

790 item_type = "STRUCT" 

791 

792 return (self.name, item_type.upper(), self.values) 

793 

794 def __eq__(self, other): 

795 if not isinstance(other, ArrayQueryParameter): 

796 return NotImplemented 

797 return self._key() == other._key() 

798 

799 def __ne__(self, other): 

800 return not self == other 

801 

802 def __repr__(self): 

803 return "ArrayQueryParameter{}".format(self._key()) 

804 

805 

806class StructQueryParameter(_AbstractQueryParameter): 

807 """Name / positional query parameters for struct values. 

808 

809 Args: 

810 name (Optional[str]): 

811 Parameter name, used via ``@foo`` syntax. If None, the 

812 parameter can only be addressed via position (``?``). 

813 

814 sub_params (Union[Tuple[ 

815 google.cloud.bigquery.query.ScalarQueryParameter, 

816 google.cloud.bigquery.query.ArrayQueryParameter, 

817 google.cloud.bigquery.query.StructQueryParameter 

818 ]]): The sub-parameters for the struct 

819 """ 

820 

821 def __init__(self, name, *sub_params) -> None: 

822 self.name = name 

823 self.struct_types: Dict[str, Any] = OrderedDict() 

824 self.struct_values: Dict[str, Any] = {} 

825 

826 types = self.struct_types 

827 values = self.struct_values 

828 for sub in sub_params: 

829 if isinstance(sub, self.__class__): 

830 types[sub.name] = "STRUCT" 

831 values[sub.name] = sub 

832 elif isinstance(sub, ArrayQueryParameter): 

833 types[sub.name] = "ARRAY" 

834 values[sub.name] = sub 

835 else: 

836 types[sub.name] = sub.type_ 

837 values[sub.name] = sub.value 

838 

839 @classmethod 

840 def positional(cls, *sub_params): 

841 """Factory for positional parameters. 

842 

843 Args: 

844 sub_params (Union[Tuple[ 

845 google.cloud.bigquery.query.ScalarQueryParameter, 

846 google.cloud.bigquery.query.ArrayQueryParameter, 

847 google.cloud.bigquery.query.StructQueryParameter 

848 ]]): The sub-parameters for the struct 

849 

850 Returns: 

851 google.cloud.bigquery.query.StructQueryParameter: Instance without name 

852 """ 

853 return cls(None, *sub_params) 

854 

855 @classmethod 

856 def from_api_repr(cls, resource: dict) -> "StructQueryParameter": 

857 """Factory: construct parameter from JSON resource. 

858 

859 Args: 

860 resource (Dict): JSON mapping of parameter 

861 

862 Returns: 

863 google.cloud.bigquery.query.StructQueryParameter: Instance 

864 """ 

865 # Import here to avoid circular imports. 

866 from google.cloud.bigquery import schema 

867 

868 name = resource.get("name") 

869 instance = cls(name) 

870 type_resources = {} 

871 types = instance.struct_types 

872 for item in resource["parameterType"]["structTypes"]: 

873 types[item["name"]] = item["type"]["type"] 

874 type_resources[item["name"]] = item["type"] 

875 struct_values = resource["parameterValue"]["structValues"] 

876 for key, value in struct_values.items(): 

877 type_ = types[key] 

878 converted: Optional[Union[ArrayQueryParameter, StructQueryParameter]] = None 

879 if type_ == "STRUCT": 

880 struct_resource = { 

881 "name": key, 

882 "parameterType": type_resources[key], 

883 "parameterValue": value, 

884 } 

885 converted = StructQueryParameter.from_api_repr(struct_resource) 

886 elif type_ == "ARRAY": 

887 struct_resource = { 

888 "name": key, 

889 "parameterType": type_resources[key], 

890 "parameterValue": value, 

891 } 

892 converted = ArrayQueryParameter.from_api_repr(struct_resource) 

893 else: 

894 value = value["value"] 

895 converted = _helpers.SCALAR_QUERY_PARAM_PARSER.to_py( 

896 value, schema.SchemaField(cast(str, name), type_) 

897 ) 

898 instance.struct_values[key] = converted 

899 return instance 

900 

901 def to_api_repr(self) -> dict: 

902 """Construct JSON API representation for the parameter. 

903 

904 Returns: 

905 Dict: JSON mapping 

906 """ 

907 s_types = {} 

908 values = {} 

909 for name, value in self.struct_values.items(): 

910 type_ = self.struct_types[name] 

911 if type_ in ("STRUCT", "ARRAY"): 

912 repr_ = value.to_api_repr() 

913 s_types[name] = {"name": name, "type": repr_["parameterType"]} 

914 values[name] = repr_["parameterValue"] 

915 else: 

916 s_types[name] = {"name": name, "type": {"type": type_}} 

917 converter = _SCALAR_VALUE_TO_JSON_PARAM.get(type_, lambda value: value) 

918 values[name] = {"value": converter(value)} 

919 

920 resource = { 

921 "parameterType": { 

922 "type": "STRUCT", 

923 "structTypes": [s_types[key] for key in self.struct_types], 

924 }, 

925 "parameterValue": {"structValues": values}, 

926 } 

927 if self.name is not None: 

928 resource["name"] = self.name 

929 return resource 

930 

931 def _key(self): 

932 """A tuple key that uniquely describes this field. 

933 

934 Used to compute this instance's hashcode and evaluate equality. 

935 

936 Returns: 

937 Tuple: The contents of this :class:`~google.cloud.bigquery.ArrayQueryParameter`. 

938 """ 

939 return (self.name, self.struct_types, self.struct_values) 

940 

941 def __eq__(self, other): 

942 if not isinstance(other, StructQueryParameter): 

943 return NotImplemented 

944 return self._key() == other._key() 

945 

946 def __ne__(self, other): 

947 return not self == other 

948 

949 def __repr__(self): 

950 return "StructQueryParameter{}".format(self._key()) 

951 

952 

953class RangeQueryParameter(_AbstractQueryParameter): 

954 """Named / positional query parameters for range values. 

955 

956 Args: 

957 range_element_type (Union[str, RangeQueryParameterType]): 

958 The type of range elements. It must be one of 'TIMESTAMP', 

959 'DATE', or 'DATETIME'. 

960 

961 start (Optional[Union[ScalarQueryParameter, str]]): 

962 The start of the range value. Must be the same type as 

963 range_element_type. If not provided, it's interpreted as UNBOUNDED. 

964 

965 end (Optional[Union[ScalarQueryParameter, str]]): 

966 The end of the range value. Must be the same type as 

967 range_element_type. If not provided, it's interpreted as UNBOUNDED. 

968 

969 name (Optional[str]): 

970 Parameter name, used via ``@foo`` syntax. If None, the 

971 parameter can only be addressed via position (``?``). 

972 """ 

973 

974 @classmethod 

975 def _parse_range_element_type(self, range_element_type): 

976 if isinstance(range_element_type, str): 

977 if range_element_type not in _SUPPORTED_RANGE_ELEMENTS: 

978 raise ValueError( 

979 "If given as a string, range_element_type must be one of " 

980 f"'TIMESTAMP', 'DATE', or 'DATETIME'. Got {range_element_type}." 

981 ) 

982 return RangeQueryParameterType(range_element_type) 

983 elif isinstance(range_element_type, RangeQueryParameterType): 

984 if range_element_type.type_._type not in _SUPPORTED_RANGE_ELEMENTS: 

985 raise ValueError( 

986 "If given as a RangeQueryParameterType object, " 

987 "range_element_type must be one of 'TIMESTAMP', 'DATE', " 

988 "or 'DATETIME' type." 

989 ) 

990 return range_element_type 

991 else: 

992 raise ValueError( 

993 "range_element_type must be a string or " 

994 "RangeQueryParameterType object, of 'TIMESTAMP', 'DATE', " 

995 "or 'DATETIME' type. Got " 

996 f"{type(range_element_type)}:{range_element_type}" 

997 ) 

998 

999 @classmethod 

1000 def _serialize_range_element_value(self, value, type_): 

1001 if value is None or isinstance(value, str): 

1002 return value 

1003 else: 

1004 converter = _SCALAR_VALUE_TO_JSON_PARAM.get(type_) 

1005 if converter is not None: 

1006 return converter(value) # type: ignore 

1007 else: 

1008 raise ValueError( 

1009 f"Cannot convert range element value from type {type_}, " 

1010 "must be one of the strings 'TIMESTAMP', 'DATE' " 

1011 "'DATETIME' or a RangeQueryParameterType object." 

1012 ) 

1013 

1014 def __init__( 

1015 self, 

1016 range_element_type, 

1017 start=None, 

1018 end=None, 

1019 name=None, 

1020 ): 

1021 self.name = name 

1022 self.range_element_type = self._parse_range_element_type(range_element_type) 

1023 print(self.range_element_type.type_._type) 

1024 self.start = start 

1025 self.end = end 

1026 

1027 @classmethod 

1028 def positional( 

1029 cls, range_element_type, start=None, end=None 

1030 ) -> "RangeQueryParameter": 

1031 """Factory for positional parameters. 

1032 

1033 Args: 

1034 range_element_type (Union[str, RangeQueryParameterType]): 

1035 The type of range elements. It must be one of `'TIMESTAMP'`, 

1036 `'DATE'`, or `'DATETIME'`. 

1037 

1038 start (Optional[Union[ScalarQueryParameter, str]]): 

1039 The start of the range value. Must be the same type as 

1040 range_element_type. If not provided, it's interpreted as 

1041 UNBOUNDED. 

1042 

1043 end (Optional[Union[ScalarQueryParameter, str]]): 

1044 The end of the range value. Must be the same type as 

1045 range_element_type. If not provided, it's interpreted as 

1046 UNBOUNDED. 

1047 

1048 Returns: 

1049 google.cloud.bigquery.query.RangeQueryParameter: Instance without 

1050 name. 

1051 """ 

1052 return cls(range_element_type, start, end) 

1053 

1054 @classmethod 

1055 def from_api_repr(cls, resource: dict) -> "RangeQueryParameter": 

1056 """Factory: construct parameter from JSON resource. 

1057 

1058 Args: 

1059 resource (Dict): JSON mapping of parameter 

1060 

1061 Returns: 

1062 google.cloud.bigquery.query.RangeQueryParameter: Instance 

1063 """ 

1064 name = resource.get("name") 

1065 range_element_type = ( 

1066 resource.get("parameterType", {}).get("rangeElementType", {}).get("type") 

1067 ) 

1068 range_value = resource.get("parameterValue", {}).get("rangeValue", {}) 

1069 start = range_value.get("start", {}).get("value") 

1070 end = range_value.get("end", {}).get("value") 

1071 

1072 return cls(range_element_type, start=start, end=end, name=name) 

1073 

1074 def to_api_repr(self) -> dict: 

1075 """Construct JSON API representation for the parameter. 

1076 

1077 Returns: 

1078 Dict: JSON mapping 

1079 """ 

1080 range_element_type = self.range_element_type.to_api_repr() 

1081 type_ = self.range_element_type.type_._type 

1082 start = self._serialize_range_element_value(self.start, type_) 

1083 end = self._serialize_range_element_value(self.end, type_) 

1084 resource = { 

1085 "parameterType": range_element_type, 

1086 "parameterValue": { 

1087 "rangeValue": { 

1088 "start": {"value": start}, 

1089 "end": {"value": end}, 

1090 }, 

1091 }, 

1092 } 

1093 

1094 # distinguish between name not provided vs. name being empty string 

1095 if self.name is not None: 

1096 resource["name"] = self.name 

1097 

1098 return resource 

1099 

1100 def _key(self): 

1101 """A tuple key that uniquely describes this field. 

1102 

1103 Used to compute this instance's hashcode and evaluate equality. 

1104 

1105 Returns: 

1106 Tuple: The contents of this 

1107 :class:`~google.cloud.bigquery.query.RangeQueryParameter`. 

1108 """ 

1109 

1110 range_element_type = self.range_element_type.to_api_repr() 

1111 return (self.name, range_element_type, self.start, self.end) 

1112 

1113 def __eq__(self, other): 

1114 if not isinstance(other, RangeQueryParameter): 

1115 return NotImplemented 

1116 return self._key() == other._key() 

1117 

1118 def __ne__(self, other): 

1119 return not self == other 

1120 

1121 def __repr__(self): 

1122 return "RangeQueryParameter{}".format(self._key()) 

1123 

1124 

1125class SqlParameterScalarTypes: 

1126 """Supported scalar SQL query parameter types as type objects.""" 

1127 

1128 BOOL = ScalarQueryParameterType("BOOL") 

1129 BOOLEAN = ScalarQueryParameterType("BOOL") 

1130 BIGDECIMAL = ScalarQueryParameterType("BIGNUMERIC") 

1131 BIGNUMERIC = ScalarQueryParameterType("BIGNUMERIC") 

1132 BYTES = ScalarQueryParameterType("BYTES") 

1133 DATE = ScalarQueryParameterType("DATE") 

1134 DATETIME = ScalarQueryParameterType("DATETIME") 

1135 DECIMAL = ScalarQueryParameterType("NUMERIC") 

1136 FLOAT = ScalarQueryParameterType("FLOAT64") 

1137 FLOAT64 = ScalarQueryParameterType("FLOAT64") 

1138 GEOGRAPHY = ScalarQueryParameterType("GEOGRAPHY") 

1139 INT64 = ScalarQueryParameterType("INT64") 

1140 INTEGER = ScalarQueryParameterType("INT64") 

1141 NUMERIC = ScalarQueryParameterType("NUMERIC") 

1142 STRING = ScalarQueryParameterType("STRING") 

1143 TIME = ScalarQueryParameterType("TIME") 

1144 TIMESTAMP = ScalarQueryParameterType("TIMESTAMP") 

1145 

1146 

1147class _QueryResults(object): 

1148 """Results of a query. 

1149 

1150 See: 

1151 https://g.co/cloud/bigquery/docs/reference/rest/v2/jobs/getQueryResults 

1152 """ 

1153 

1154 def __init__(self, properties): 

1155 self._properties = {} 

1156 self._set_properties(properties) 

1157 

1158 @classmethod 

1159 def from_api_repr(cls, api_response): 

1160 return cls(api_response) 

1161 

1162 @property 

1163 def project(self): 

1164 """Project bound to the query job. 

1165 

1166 Returns: 

1167 str: The project that the query job is associated with. 

1168 """ 

1169 return self._properties.get("jobReference", {}).get("projectId") 

1170 

1171 @property 

1172 def cache_hit(self): 

1173 """Query results served from cache. 

1174 

1175 See: 

1176 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.cache_hit 

1177 

1178 Returns: 

1179 Optional[bool]: 

1180 True if the query results were served from cache (None 

1181 until set by the server). 

1182 """ 

1183 return self._properties.get("cacheHit") 

1184 

1185 @property 

1186 def complete(self): 

1187 """Server completed query. 

1188 

1189 See: 

1190 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.job_complete 

1191 

1192 Returns: 

1193 Optional[bool]: 

1194 True if the query completed on the server (None 

1195 until set by the server). 

1196 """ 

1197 return self._properties.get("jobComplete") 

1198 

1199 @property 

1200 def errors(self): 

1201 """Errors generated by the query. 

1202 

1203 See: 

1204 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.errors 

1205 

1206 Returns: 

1207 Optional[List[Mapping]]: 

1208 Mappings describing errors generated on the server (None 

1209 until set by the server). 

1210 """ 

1211 return self._properties.get("errors") 

1212 

1213 @property 

1214 def job_id(self): 

1215 """Job ID of the query job these results are from. 

1216 

1217 See: 

1218 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.job_reference 

1219 

1220 Returns: 

1221 str: Job ID of the query job. 

1222 """ 

1223 return self._properties.get("jobReference", {}).get("jobId") 

1224 

1225 @property 

1226 def location(self): 

1227 """Location of the query job these results are from. 

1228 

1229 See: 

1230 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.job_reference 

1231 

1232 Returns: 

1233 str: Job ID of the query job. 

1234 """ 

1235 return self._properties.get("jobReference", {}).get("location") 

1236 

1237 @property 

1238 def query_id(self) -> Optional[str]: 

1239 """[Preview] ID of a completed query. 

1240 

1241 This ID is auto-generated and not guaranteed to be populated. 

1242 """ 

1243 return self._properties.get("queryId") 

1244 

1245 @property 

1246 def page_token(self): 

1247 """Token for fetching next bach of results. 

1248 

1249 See: 

1250 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.page_token 

1251 

1252 Returns: 

1253 Optional[str]: Token generated on the server (None until set by the server). 

1254 """ 

1255 return self._properties.get("pageToken") 

1256 

1257 @property 

1258 def total_rows(self): 

1259 """Total number of rows returned by the query. 

1260 

1261 See: 

1262 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.total_rows 

1263 

1264 Returns: 

1265 Optional[int]: Count generated on the server (None until set by the server). 

1266 """ 

1267 total_rows = self._properties.get("totalRows") 

1268 if total_rows is not None: 

1269 return int(total_rows) 

1270 

1271 @property 

1272 def total_bytes_processed(self): 

1273 """Total number of bytes processed by the query. 

1274 

1275 See: 

1276 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.total_bytes_processed 

1277 

1278 Returns: 

1279 Optional[int]: Count generated on the server (None until set by the server). 

1280 """ 

1281 total_bytes_processed = self._properties.get("totalBytesProcessed") 

1282 if total_bytes_processed is not None: 

1283 return int(total_bytes_processed) 

1284 

1285 @property 

1286 def num_dml_affected_rows(self): 

1287 """Total number of rows affected by a DML query. 

1288 

1289 See: 

1290 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.num_dml_affected_rows 

1291 

1292 Returns: 

1293 Optional[int]: Count generated on the server (None until set by the server). 

1294 """ 

1295 num_dml_affected_rows = self._properties.get("numDmlAffectedRows") 

1296 if num_dml_affected_rows is not None: 

1297 return int(num_dml_affected_rows) 

1298 

1299 @property 

1300 def rows(self): 

1301 """Query results. 

1302 

1303 See: 

1304 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.rows 

1305 

1306 Returns: 

1307 Optional[List[google.cloud.bigquery.table.Row]]: 

1308 Rows containing the results of the query. 

1309 """ 

1310 return _rows_from_json(self._properties.get("rows", ()), self.schema) 

1311 

1312 @property 

1313 def schema(self): 

1314 """Schema for query results. 

1315 

1316 See: 

1317 https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#body.QueryResponse.FIELDS.schema 

1318 

1319 Returns: 

1320 Optional[List[SchemaField]]: 

1321 Fields describing the schema (None until set by the server). 

1322 """ 

1323 return _parse_schema_resource(self._properties.get("schema", {})) 

1324 

1325 def _set_properties(self, api_response): 

1326 """Update properties from resource in body of ``api_response`` 

1327 

1328 Args: 

1329 api_response (Dict): Response returned from an API call 

1330 """ 

1331 self._properties.clear() 

1332 self._properties.update(copy.deepcopy(api_response)) 

1333 

1334 

1335def _query_param_from_api_repr(resource): 

1336 """Helper: Construct concrete query parameter from JSON resource.""" 

1337 qp_type = resource["parameterType"] 

1338 if "arrayType" in qp_type: 

1339 klass = ArrayQueryParameter 

1340 elif "structTypes" in qp_type: 

1341 klass = StructQueryParameter 

1342 else: 

1343 klass = ScalarQueryParameter 

1344 return klass.from_api_repr(resource)