Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for query parameters #2776

Merged
merged 20 commits into from
Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bigquery/google/cloud/bigquery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@
from google.cloud.bigquery.dataset import Dataset
from google.cloud.bigquery.schema import SchemaField
from google.cloud.bigquery.table import Table
from google.cloud.bigquery._helpers import ArrayQueryParameter

This comment was marked as spam.

from google.cloud.bigquery._helpers import ScalarQueryParameter
from google.cloud.bigquery._helpers import StructQueryParameter
263 changes: 253 additions & 10 deletions bigquery/google/cloud/bigquery/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,259 @@ def __set__(self, instance, value):
instance._udf_resources = tuple(value)


def _build_udf_resources(resources):
class AbstractQueryParameter(object):
"""Base class for named / positional query parameters.
"""

This comment was marked as spam.

:type resources: sequence of :class:`UDFResource`
:param resources: fields to be appended.
@classmethod
def from_api_repr(cls, resource):
"""Factory: construct paramter from JSON resource.

:rtype: mapping
:returns: a mapping describing userDefinedFunctionResources for the query.
:type resource: dict
:param resource: JSON mapping of parameter

:rtype: :class:`ScalarQueryParameter`
"""
raise NotImplementedError

def to_api_repr(self):
"""Construct JSON API representation for the parameter.

:rtype: dict
"""
raise NotImplementedError


class ScalarQueryParameter(AbstractQueryParameter):
"""Named / positional query parameters for scalar values.

:type name: str or None

This comment was marked as spam.

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

This comment was marked as spam.

This comment was marked as spam.

paramter can only be addressed via position (`?`).

This comment was marked as spam.


:type type_: str
:param type_: name of parameter type. One of `'STRING'`, `'INT64'`,
`'FLOAT64'`, `'BOOLEAN'`, `'TIMESTAMP'`, or `'DATE'`.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


:type value: str, int, float, bool, :class:`datetime.datetime`, or
:class:`datetime.date`.
:param value: the scalar parameter value.
"""
def __init__(self, name, type_, value):

This comment was marked as spam.

This comment was marked as spam.

self.name = name
self.type_ = type_
self.value = value

@classmethod
def positional(cls, type_, value):
"""Factory for positional paramters.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


:type type_: str
:param type_: name of paramter type. One of `'STRING'`, `'INT64'`,
`'FLOAT64'`, `'BOOLEAN'`, `'TIMESTAMP'`, or `'DATE'`.

:type value: str, int, float, bool, :class:`datetime.datetime`, or
:class:`datetime.date`.
:param value: the scalar parameter value.

:rtype: :class:`ScalarQueryParameter`
:returns: instance w/o name

This comment was marked as spam.

"""
return cls(None, type_, value)

@classmethod
def from_api_repr(cls, resource):
"""Factory: construct paramter from JSON resource.

:type resource: dict
:param resource: JSON mapping of parameter

:rtype: :class:`ScalarQueryParameter`
:returns: instance
"""
name = resource.get('name')
type_ = resource['parameterType']['type']
value = resource['parameterValue']['value']

This comment was marked as spam.

converted = _CELLDATA_FROM_JSON[type_](value, None)
return cls(name, type_, converted)

def to_api_repr(self):
"""Construct JSON API representation for the parameter.

:rtype: dict
:returns: JSON mapping
"""
resource = {
'parameterType': {
'type': self.type_,
},
'parameterValue': {
'value': self.value,
},
}
if self.name is not None:
resource['name'] = self.name
return resource


class ArrayQueryParameter(AbstractQueryParameter):
"""Named / positional query parameters for array values.

:type name: str or None
:param name: Parameter name, used via `@foo` syntax. If None, the
paramter can only be addressed via position (`?`).

:type array_type: str
:param array_type:

This comment was marked as spam.

This comment was marked as spam.

name of type of array elements. One of `'STRING'`, `'INT64'`,
`'FLOAT64'`, `'BOOLEAN'`, `'TIMESTAMP'`, or `'DATE'`.

:type values: list of appropriate scalar type.
:param values: the parameter array values.
"""
def __init__(self, name, array_type, values):
self.name = name
self.array_type = array_type
self.values = values

@classmethod
def positional(cls, array_type, values):
"""Factory for positional paramters.

:type array_type: str
:param array_type:
name of type of array elements. One of `'STRING'`, `'INT64'`,
`'FLOAT64'`, `'BOOLEAN'`, `'TIMESTAMP'`, or `'DATE'`.

:type values: list of appropriate scalar type
:param values: the parameter array values.

:rtype: :class:`ArrayQueryParameter`
:returns: instance w/o name
"""
return cls(None, array_type, values)

@classmethod
def from_api_repr(cls, resource):
"""Factory: construct paramter from JSON resource.

:type resource: dict
:param resource: JSON mapping of parameter

:rtype: :class:`ArrayQueryParameter`
:returns: instance
"""
name = resource.get('name')
array_type = resource['parameterType']['arrayType']
values = resource['parameterValue']['arrayValues']
converted = [
_CELLDATA_FROM_JSON[array_type](value, None) for value in values]

This comment was marked as spam.

This comment was marked as spam.

return cls(name, array_type, converted)

def to_api_repr(self):
"""Construct JSON API representation for the parameter.

:rtype: dict
:returns: JSON mapping
"""
resource = {
'parameterType': {
'arrayType': self.array_type,
},
'parameterValue': {
'arrayValues': self.values,
},
}
if self.name is not None:
resource['name'] = self.name
return resource


class StructQueryParameter(AbstractQueryParameter):
"""Named / positional query parameters for struct values.

:type name: str or None
:param name: Parameter name, used via `@foo` syntax. If None, the
paramter can only be addressed via position (`?`).

:type sub_parms: tuple of :class:`ScalarQueryParameter`
:param sub_parms: the sub-parameters for the struct

This comment was marked as spam.

"""
udfs = []
for resource in resources:
udf = {resource.udf_type: resource.value}
udfs.append(udf)
return udfs
def __init__(self, name, *sub_parms):
self.name = name
self._order = [sub.name for sub in sub_parms]

This comment was marked as spam.

self.struct_types = {sub.name: sub.type_ for sub in sub_parms}
self.struct_values = {sub.name: sub.value for sub in sub_parms}

@classmethod
def positional(cls, *sub_parms):
"""Factory for positional paramters.

:type sub_parms: tuple of :class:`ScalarQueryParameter`
:param sub_parms:s the sub-parameters for the struct

This comment was marked as spam.


:rtype: :class:`StructQueryParameter`
:returns: instance w/o name
"""
return cls(None, *sub_parms)

@classmethod
def from_api_repr(cls, resource):
"""Factory: construct paramter from JSON resource.

:type resource: dict
:param resource: JSON mapping of parameter

:rtype: :class:`StructQueryParameter`
:returns: instance
"""
name = resource.get('name')
instance = cls(name)
types = instance.struct_types = {
item['name']: item['type']
for item in resource['parameterType']['structTypes']
}

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

struct_values = resource['parameterValue']['structValues']
values = instance.struct_values = {}
for key, value in struct_values.items():
values[key] = _CELLDATA_FROM_JSON[types[key]](value, None)
return instance

def to_api_repr(self):
"""Construct JSON API representation for the parameter.

:rtype: dict
:returns: JSON mapping
"""
types = [
{'name': name, 'type': self.struct_types[name]}
for name in self._order
]
resource = {
'parameterType': {
'structTypes': types,
},
'parameterValue': {
'structValues': self.struct_values,
},
}
if self.name is not None:
resource['name'] = self.name
return resource


class QueryParametersProperty(object):
"""Custom property type, holding query parameter instances."""

def __get__(self, instance, owner):
"""Descriptor protocol: accessor"""
if instance is None:
return self
return list(instance._query_parameters)

def __set__(self, instance, value):
"""Descriptor protocol: mutator"""
if not all(isinstance(u, AbstractQueryParameter) for u in value):

This comment was marked as spam.

This comment was marked as spam.

raise ValueError(
"query parameters must be derived from AbstractQueryParameter")
instance._query_parameters = tuple(value)
35 changes: 31 additions & 4 deletions bigquery/google/cloud/bigquery/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ def extract_table_to_storage(self, job_name, source, *destination_uris):
return ExtractTableToStorageJob(job_name, source, destination_uris,
client=self)

def run_async_query(self, job_name, query):
def run_async_query(self, job_name, query,
udf_resources=(), query_parameters=()):
"""Construct a job for running a SQL query asynchronously.

See:
Expand All @@ -287,21 +288,47 @@ def run_async_query(self, job_name, query):
:type query: str
:param query: SQL query to be executed

:type udf_resources: tuple
:param udf_resources: An iterable of
:class:`google.cloud.bigquery._helpers.UDFResource`
(empty by default)

:type query_parameters: tuple
:param query_parameters:
An iterable of
:class:`google.cloud.bigquery._helpers.AbstractQueryParameter`
(empty by default)

:rtype: :class:`google.cloud.bigquery.job.QueryJob`
:returns: a new ``QueryJob`` instance
"""
return QueryJob(job_name, query, client=self)
return QueryJob(job_name, query, client=self,
udf_resources=udf_resources,
query_parameters=query_parameters)

def run_sync_query(self, query):
def run_sync_query(self, query, udf_resources=(), query_parameters=()):
"""Run a SQL query synchronously.

:type query: str
:param query: SQL query to be executed

:type udf_resources: tuple
:param udf_resources: An iterable of
:class:`google.cloud.bigquery._helpers.UDFResource`
(empty by default)

:type query_parameters: tuple
:param query_parameters:
An iterable of
:class:`google.cloud.bigquery._helpers.AbstractQueryParameter`
(empty by default)

:rtype: :class:`google.cloud.bigquery.query.QueryResults`
:returns: a new ``QueryResults`` instance
"""
return QueryResults(query, client=self)
return QueryResults(query, client=self,
udf_resources=udf_resources,
query_parameters=query_parameters)


# pylint: disable=unused-argument
Expand Down
Loading