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

383 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

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, Optional, Dict, Union 

22 

23from google.cloud.bigquery.table import _parse_schema_resource 

24from google.cloud.bigquery._helpers import _rows_from_json 

25from google.cloud.bigquery._helpers import _QUERY_PARAMS_FROM_JSON 

26from google.cloud.bigquery._helpers import _SCALAR_VALUE_TO_JSON_PARAM 

27 

28 

29_SCALAR_VALUE_TYPE = Optional[ 

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

31] 

32 

33 

34class ConnectionProperty: 

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

36 

37 See 

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

39 

40 Args: 

41 key: 

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

43 ``'session_id'``. 

44 value: The value of the property to set. 

45 """ 

46 

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

48 self._properties = { 

49 "key": key, 

50 "value": value, 

51 } 

52 

53 @property 

54 def key(self) -> str: 

55 """Name of the property. 

56 

57 For example: 

58 

59 * ``time_zone`` 

60 * ``session_id`` 

61 """ 

62 return self._properties["key"] 

63 

64 @property 

65 def value(self) -> str: 

66 """Value of the property.""" 

67 return self._properties["value"] 

68 

69 @classmethod 

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

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

72 from JSON resource. 

73 

74 Args: 

75 resource: JSON representation. 

76 

77 Returns: 

78 A connection property. 

79 """ 

80 value = cls() 

81 value._properties = resource 

82 return value 

83 

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

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

86 

87 Returns: 

88 JSON mapping 

89 """ 

90 return self._properties 

91 

92 

93class UDFResource(object): 

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

95 

96 Args: 

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

98 

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

100 

101 See: 

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

103 """ 

104 

105 def __init__(self, udf_type, value): 

106 self.udf_type = udf_type 

107 self.value = value 

108 

109 def __eq__(self, other): 

110 if not isinstance(other, UDFResource): 

111 return NotImplemented 

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

113 

114 def __ne__(self, other): 

115 return not self == other 

116 

117 

118class _AbstractQueryParameterType: 

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

120 

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

122 """ 

123 

124 @classmethod 

125 def from_api_repr(cls, resource): 

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

127 

128 Args: 

129 resource (Dict): JSON mapping of parameter 

130 

131 Returns: 

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

133 """ 

134 raise NotImplementedError 

135 

136 def to_api_repr(self): 

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

138 

139 Returns: 

140 Dict: JSON mapping 

141 """ 

142 raise NotImplementedError 

143 

144 

145class ScalarQueryParameterType(_AbstractQueryParameterType): 

146 """Type representation for scalar query parameters. 

147 

148 Args: 

149 type_ (str): 

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

151 'DATETIME', or 'DATE'. 

152 name (Optional[str]): 

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

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

155 description (Optional[str]): 

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

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

158 """ 

159 

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

161 self._type = type_ 

162 self.name = name 

163 self.description = description 

164 

165 @classmethod 

166 def from_api_repr(cls, resource): 

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

168 

169 Args: 

170 resource (Dict): JSON mapping of parameter 

171 

172 Returns: 

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

174 """ 

175 type_ = resource["type"] 

176 return cls(type_) 

177 

178 def to_api_repr(self): 

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

180 

181 Returns: 

182 Dict: JSON mapping 

183 """ 

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

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

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

187 return {"type": self._type} 

188 

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

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

191 

192 Args: 

193 name (Union[str, None]): 

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

195 name is cleared. 

196 

197 Returns: 

198 google.cloud.bigquery.query.ScalarQueryParameterType: 

199 A new instance with updated name. 

200 """ 

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

202 

203 def __repr__(self): 

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

205 description = ( 

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

207 if self.description is not None 

208 else "" 

209 ) 

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

211 

212 

213class ArrayQueryParameterType(_AbstractQueryParameterType): 

214 """Type representation for array query parameters. 

215 

216 Args: 

217 array_type (Union[ScalarQueryParameterType, StructQueryParameterType]): 

218 The type of array elements. 

219 name (Optional[str]): 

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

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

222 description (Optional[str]): 

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

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

225 """ 

226 

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

228 self._array_type = array_type 

229 self.name = name 

230 self.description = description 

231 

232 @classmethod 

233 def from_api_repr(cls, resource): 

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

235 

236 Args: 

237 resource (Dict): JSON mapping of parameter 

238 

239 Returns: 

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

241 """ 

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

243 

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

245 klass = StructQueryParameterType 

246 else: 

247 klass = ScalarQueryParameterType 

248 

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

250 return cls(item_type_instance) 

251 

252 def to_api_repr(self): 

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

254 

255 Returns: 

256 Dict: JSON mapping 

257 """ 

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

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

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

261 return { 

262 "type": "ARRAY", 

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

264 } 

265 

266 def __repr__(self): 

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

268 description = ( 

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

270 if self.description is not None 

271 else "" 

272 ) 

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

274 

275 

276class StructQueryParameterType(_AbstractQueryParameterType): 

277 """Type representation for struct query parameters. 

278 

279 Args: 

280 fields (Iterable[Union[ \ 

281 ArrayQueryParameterType, ScalarQueryParameterType, StructQueryParameterType \ 

282 ]]): 

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

284 name (Optional[str]): 

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

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

287 description (Optional[str]): 

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

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

290 """ 

291 

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

293 if not fields: 

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

295 

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

297 self.name = name 

298 self.description = description 

299 

300 @property 

301 def fields(self): 

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

303 

304 @classmethod 

305 def from_api_repr(cls, resource): 

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

307 

308 Args: 

309 resource (Dict): JSON mapping of parameter 

310 

311 Returns: 

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

313 """ 

314 fields = [] 

315 

316 for struct_field in resource["structTypes"]: 

317 type_repr = struct_field["type"] 

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

319 klass = StructQueryParameterType 

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

321 klass = ArrayQueryParameterType 

322 else: 

323 klass = ScalarQueryParameterType 

324 

325 type_instance = klass.from_api_repr(type_repr) 

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

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

328 fields.append(type_instance) 

329 

330 return cls(*fields) 

331 

332 def to_api_repr(self): 

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

334 

335 Returns: 

336 Dict: JSON mapping 

337 """ 

338 fields = [] 

339 

340 for field in self._fields: 

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

342 if field.name is not None: 

343 item["name"] = field.name 

344 if field.description is not None: 

345 item["description"] = field.description 

346 

347 fields.append(item) 

348 

349 return { 

350 "type": "STRUCT", 

351 "structTypes": fields, 

352 } 

353 

354 def __repr__(self): 

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

356 description = ( 

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

358 if self.description is not None 

359 else "" 

360 ) 

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

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

363 

364 

365class _AbstractQueryParameter(object): 

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

367 

368 @classmethod 

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

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

371 

372 Args: 

373 resource (Dict): JSON mapping of parameter 

374 

375 Returns: 

376 A new instance of _AbstractQueryParameter subclass. 

377 """ 

378 raise NotImplementedError 

379 

380 def to_api_repr(self) -> dict: 

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

382 

383 Returns: 

384 Dict: JSON representation for the parameter. 

385 """ 

386 raise NotImplementedError 

387 

388 

389class ScalarQueryParameter(_AbstractQueryParameter): 

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

391 

392 Args: 

393 name: 

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

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

396 

397 type_: 

398 Name of parameter type. See 

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

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

401 supported types. 

402 

403 value: 

404 The scalar parameter value. 

405 """ 

406 

407 def __init__( 

408 self, 

409 name: Optional[str], 

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

411 value: _SCALAR_VALUE_TYPE, 

412 ): 

413 self.name = name 

414 if isinstance(type_, ScalarQueryParameterType): 

415 self.type_ = type_._type 

416 else: 

417 self.type_ = type_ 

418 self.value = value 

419 

420 @classmethod 

421 def positional( 

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

423 ) -> "ScalarQueryParameter": 

424 """Factory for positional paramater. 

425 

426 Args: 

427 type_: 

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

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

430 'DATE'. 

431 

432 value: 

433 The scalar parameter value. 

434 

435 Returns: 

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

437 """ 

438 return cls(None, type_, value) 

439 

440 @classmethod 

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

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

443 

444 Args: 

445 resource (Dict): JSON mapping of parameter 

446 

447 Returns: 

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

449 """ 

450 name = resource.get("name") 

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

452 

453 # parameterValue might not be present if JSON resource originates 

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

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

456 if value is not None: 

457 converted = _QUERY_PARAMS_FROM_JSON[type_](value, None) 

458 else: 

459 converted = None 

460 

461 return cls(name, type_, converted) 

462 

463 def to_api_repr(self) -> dict: 

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

465 

466 Returns: 

467 Dict: JSON mapping 

468 """ 

469 value = self.value 

470 converter = _SCALAR_VALUE_TO_JSON_PARAM.get(self.type_) 

471 if converter is not None: 

472 value = converter(value) 

473 resource: Dict[str, Any] = { 

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

475 "parameterValue": {"value": value}, 

476 } 

477 if self.name is not None: 

478 resource["name"] = self.name 

479 return resource 

480 

481 def _key(self): 

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

483 

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

485 

486 Returns: 

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

488 """ 

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

490 

491 def __eq__(self, other): 

492 if not isinstance(other, ScalarQueryParameter): 

493 return NotImplemented 

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

495 

496 def __ne__(self, other): 

497 return not self == other 

498 

499 def __repr__(self): 

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

501 

502 

503class ArrayQueryParameter(_AbstractQueryParameter): 

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

505 

506 Args: 

507 name (Optional[str]): 

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

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

510 

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

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

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

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

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

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

517 instance needs to be passed in. 

518 

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

520 """ 

521 

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

523 self.name = name 

524 self.values = values 

525 

526 if isinstance(array_type, str): 

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

528 raise ValueError( 

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

530 "please provide a StructQueryParameterType instance." 

531 ) 

532 self.array_type = array_type 

533 

534 @classmethod 

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

536 """Factory for positional parameters. 

537 

538 Args: 

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

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

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

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

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

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

545 instance needs to be passed in. 

546 

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

548 

549 Returns: 

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

551 """ 

552 return cls(None, array_type, values) 

553 

554 @classmethod 

555 def _from_api_repr_struct(cls, resource): 

556 name = resource.get("name") 

557 converted = [] 

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

559 # parse code. 

560 resource_template = { 

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

562 "parameterType": resource["parameterType"]["arrayType"] 

563 } 

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

565 struct_resource = copy.deepcopy(resource_template) 

566 struct_resource["parameterValue"] = array_value 

567 struct_value = StructQueryParameter.from_api_repr(struct_resource) 

568 converted.append(struct_value) 

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

570 

571 @classmethod 

572 def _from_api_repr_scalar(cls, resource): 

573 name = resource.get("name") 

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

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

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

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

578 converted = [ 

579 _QUERY_PARAMS_FROM_JSON[array_type](value, None) for value in values 

580 ] 

581 return cls(name, array_type, converted) 

582 

583 @classmethod 

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

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

586 

587 Args: 

588 resource (Dict): JSON mapping of parameter 

589 

590 Returns: 

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

592 """ 

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

594 if array_type == "STRUCT": 

595 return cls._from_api_repr_struct(resource) 

596 return cls._from_api_repr_scalar(resource) 

597 

598 def to_api_repr(self) -> dict: 

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

600 

601 Returns: 

602 Dict: JSON mapping 

603 """ 

604 values = self.values 

605 

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

607 self.array_type, StructQueryParameterType 

608 ): 

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

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

611 

612 if reprs: 

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

614 else: 

615 # This assertion always evaluates to True because the 

616 # constructor disallows STRUCT/RECORD type defined as a 

617 # string with empty values. 

618 assert isinstance(self.array_type, StructQueryParameterType) 

619 a_type = self.array_type.to_api_repr() 

620 else: 

621 # Scalar array item type. 

622 if isinstance(self.array_type, str): 

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

624 else: 

625 a_type = self.array_type.to_api_repr() 

626 

627 converter = _SCALAR_VALUE_TO_JSON_PARAM.get(a_type["type"]) 

628 if converter is not None: 

629 values = [converter(value) for value in values] 

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

631 

632 resource = { 

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

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

635 } 

636 if self.name is not None: 

637 resource["name"] = self.name 

638 

639 return resource 

640 

641 def _key(self): 

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

643 

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

645 

646 Returns: 

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

648 """ 

649 if isinstance(self.array_type, str): 

650 item_type = self.array_type 

651 elif isinstance(self.array_type, ScalarQueryParameterType): 

652 item_type = self.array_type._type 

653 else: 

654 item_type = "STRUCT" 

655 

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

657 

658 def __eq__(self, other): 

659 if not isinstance(other, ArrayQueryParameter): 

660 return NotImplemented 

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

662 

663 def __ne__(self, other): 

664 return not self == other 

665 

666 def __repr__(self): 

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

668 

669 

670class StructQueryParameter(_AbstractQueryParameter): 

671 """Named / positional query parameters for struct values. 

672 

673 Args: 

674 name (Optional[str]): 

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

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

677 

678 sub_params (Union[Tuple[ 

679 google.cloud.bigquery.query.ScalarQueryParameter, 

680 google.cloud.bigquery.query.ArrayQueryParameter, 

681 google.cloud.bigquery.query.StructQueryParameter 

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

683 """ 

684 

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

686 self.name = name 

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

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

689 

690 types = self.struct_types 

691 values = self.struct_values 

692 for sub in sub_params: 

693 if isinstance(sub, self.__class__): 

694 types[sub.name] = "STRUCT" 

695 values[sub.name] = sub 

696 elif isinstance(sub, ArrayQueryParameter): 

697 types[sub.name] = "ARRAY" 

698 values[sub.name] = sub 

699 else: 

700 types[sub.name] = sub.type_ 

701 values[sub.name] = sub.value 

702 

703 @classmethod 

704 def positional(cls, *sub_params): 

705 """Factory for positional parameters. 

706 

707 Args: 

708 sub_params (Union[Tuple[ 

709 google.cloud.bigquery.query.ScalarQueryParameter, 

710 google.cloud.bigquery.query.ArrayQueryParameter, 

711 google.cloud.bigquery.query.StructQueryParameter 

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

713 

714 Returns: 

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

716 """ 

717 return cls(None, *sub_params) 

718 

719 @classmethod 

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

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

722 

723 Args: 

724 resource (Dict): JSON mapping of parameter 

725 

726 Returns: 

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

728 """ 

729 name = resource.get("name") 

730 instance = cls(name) 

731 type_resources = {} 

732 types = instance.struct_types 

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

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

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

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

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

738 type_ = types[key] 

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

740 if type_ == "STRUCT": 

741 struct_resource = { 

742 "name": key, 

743 "parameterType": type_resources[key], 

744 "parameterValue": value, 

745 } 

746 converted = StructQueryParameter.from_api_repr(struct_resource) 

747 elif type_ == "ARRAY": 

748 struct_resource = { 

749 "name": key, 

750 "parameterType": type_resources[key], 

751 "parameterValue": value, 

752 } 

753 converted = ArrayQueryParameter.from_api_repr(struct_resource) 

754 else: 

755 value = value["value"] 

756 converted = _QUERY_PARAMS_FROM_JSON[type_](value, None) 

757 instance.struct_values[key] = converted 

758 return instance 

759 

760 def to_api_repr(self) -> dict: 

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

762 

763 Returns: 

764 Dict: JSON mapping 

765 """ 

766 s_types = {} 

767 values = {} 

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

769 type_ = self.struct_types[name] 

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

771 repr_ = value.to_api_repr() 

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

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

774 else: 

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

776 converter = _SCALAR_VALUE_TO_JSON_PARAM.get(type_) 

777 if converter is not None: 

778 value = converter(value) 

779 values[name] = {"value": value} 

780 

781 resource = { 

782 "parameterType": { 

783 "type": "STRUCT", 

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

785 }, 

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

787 } 

788 if self.name is not None: 

789 resource["name"] = self.name 

790 return resource 

791 

792 def _key(self): 

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

794 

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

796 

797 Returns: 

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

799 """ 

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

801 

802 def __eq__(self, other): 

803 if not isinstance(other, StructQueryParameter): 

804 return NotImplemented 

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

806 

807 def __ne__(self, other): 

808 return not self == other 

809 

810 def __repr__(self): 

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

812 

813 

814class SqlParameterScalarTypes: 

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

816 

817 BOOL = ScalarQueryParameterType("BOOL") 

818 BOOLEAN = ScalarQueryParameterType("BOOL") 

819 BIGDECIMAL = ScalarQueryParameterType("BIGNUMERIC") 

820 BIGNUMERIC = ScalarQueryParameterType("BIGNUMERIC") 

821 BYTES = ScalarQueryParameterType("BYTES") 

822 DATE = ScalarQueryParameterType("DATE") 

823 DATETIME = ScalarQueryParameterType("DATETIME") 

824 DECIMAL = ScalarQueryParameterType("NUMERIC") 

825 FLOAT = ScalarQueryParameterType("FLOAT64") 

826 FLOAT64 = ScalarQueryParameterType("FLOAT64") 

827 GEOGRAPHY = ScalarQueryParameterType("GEOGRAPHY") 

828 INT64 = ScalarQueryParameterType("INT64") 

829 INTEGER = ScalarQueryParameterType("INT64") 

830 NUMERIC = ScalarQueryParameterType("NUMERIC") 

831 STRING = ScalarQueryParameterType("STRING") 

832 TIME = ScalarQueryParameterType("TIME") 

833 TIMESTAMP = ScalarQueryParameterType("TIMESTAMP") 

834 

835 

836class _QueryResults(object): 

837 """Results of a query. 

838 

839 See: 

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

841 """ 

842 

843 def __init__(self, properties): 

844 self._properties = {} 

845 self._set_properties(properties) 

846 

847 @classmethod 

848 def from_api_repr(cls, api_response): 

849 return cls(api_response) 

850 

851 @property 

852 def project(self): 

853 """Project bound to the query job. 

854 

855 Returns: 

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

857 """ 

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

859 

860 @property 

861 def cache_hit(self): 

862 """Query results served from cache. 

863 

864 See: 

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

866 

867 Returns: 

868 Optional[bool]: 

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

870 until set by the server). 

871 """ 

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

873 

874 @property 

875 def complete(self): 

876 """Server completed query. 

877 

878 See: 

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

880 

881 Returns: 

882 Optional[bool]: 

883 True if the query completed on the server (None 

884 until set by the server). 

885 """ 

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

887 

888 @property 

889 def errors(self): 

890 """Errors generated by the query. 

891 

892 See: 

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

894 

895 Returns: 

896 Optional[List[Mapping]]: 

897 Mappings describing errors generated on the server (None 

898 until set by the server). 

899 """ 

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

901 

902 @property 

903 def job_id(self): 

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

905 

906 See: 

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

908 

909 Returns: 

910 str: Job ID of the query job. 

911 """ 

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

913 

914 @property 

915 def page_token(self): 

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

917 

918 See: 

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

920 

921 Returns: 

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

923 """ 

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

925 

926 @property 

927 def total_rows(self): 

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

929 

930 See: 

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

932 

933 Returns: 

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

935 """ 

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

937 if total_rows is not None: 

938 return int(total_rows) 

939 

940 @property 

941 def total_bytes_processed(self): 

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

943 

944 See: 

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

946 

947 Returns: 

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

949 """ 

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

951 if total_bytes_processed is not None: 

952 return int(total_bytes_processed) 

953 

954 @property 

955 def num_dml_affected_rows(self): 

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

957 

958 See: 

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

960 

961 Returns: 

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

963 """ 

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

965 if num_dml_affected_rows is not None: 

966 return int(num_dml_affected_rows) 

967 

968 @property 

969 def rows(self): 

970 """Query results. 

971 

972 See: 

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

974 

975 Returns: 

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

977 Rows containing the results of the query. 

978 """ 

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

980 

981 @property 

982 def schema(self): 

983 """Schema for query results. 

984 

985 See: 

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

987 

988 Returns: 

989 Optional[List[SchemaField]]: 

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

991 """ 

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

993 

994 def _set_properties(self, api_response): 

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

996 

997 Args: 

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

999 """ 

1000 job_id_present = ( 

1001 "jobReference" in api_response 

1002 and "jobId" in api_response["jobReference"] 

1003 and "projectId" in api_response["jobReference"] 

1004 ) 

1005 if not job_id_present: 

1006 raise ValueError("QueryResult requires a job reference") 

1007 

1008 self._properties.clear() 

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

1010 

1011 

1012def _query_param_from_api_repr(resource): 

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

1014 qp_type = resource["parameterType"] 

1015 if "arrayType" in qp_type: 

1016 klass = ArrayQueryParameter 

1017 elif "structTypes" in qp_type: 

1018 klass = StructQueryParameter 

1019 else: 

1020 klass = ScalarQueryParameter 

1021 return klass.from_api_repr(resource)