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 16 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 @@ -23,6 +23,9 @@
"""


from google.cloud.bigquery._helpers import ArrayQueryParameter
from google.cloud.bigquery._helpers import ScalarQueryParameter
from google.cloud.bigquery._helpers import StructQueryParameter
from google.cloud.bigquery.client import Client
from google.cloud.bigquery.dataset import AccessGrant
from google.cloud.bigquery.dataset import Dataset
Expand Down
286 changes: 276 additions & 10 deletions bigquery/google/cloud/bigquery/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

"""Shared helper functions for BigQuery API classes."""

from collections import OrderedDict

from google.cloud._helpers import _datetime_from_microseconds
from google.cloud._helpers import _date_from_iso8601_date

Expand Down Expand Up @@ -228,16 +230,280 @@ 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.

: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


:rtype: mapping
:returns: a mapping describing userDefinedFunctionResources for the query.
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
paramter can only be addressed via position (``?``).

: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.
"""
udfs = []
for resource in resources:
udf = {resource.udf_type: resource.value}
udfs.append(udf)
return udfs
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 without name
"""
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 without 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_params: tuple of :class:`ScalarQueryParameter`
:param sub_params: the sub-parameters for the struct
"""
def __init__(self, name, *sub_params):
self.name = name
self.struct_types = OrderedDict(
(sub.name, sub.type_) for sub in sub_params)
self.struct_values = {sub.name: sub.value for sub in sub_params}

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

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

:rtype: :class:`StructQueryParameter`
:returns: instance without name
"""
return cls(None, *sub_params)

@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': key, 'type': value}
for key, value in self.struct_types.items()

This comment was marked as spam.

This comment was marked as spam.

]
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

:type instance: :class:`QueryParametersProperty`
:param instance: instance owning the property (None if accessed via
the class).

:type owner: type
:param owner: the class owning the property.

:rtype: list of instances of classes derived from
:class:`AbstractQueryParameter`.
:returns: the descriptor, if accessed via the class, or the instance's
query paramters.
"""
if instance is None:
return self
return list(instance._query_parameters)

def __set__(self, instance, value):
"""Descriptor protocol: mutator

:type instance: :class:`QueryParametersProperty`
:param instance: instance owning the property (None if accessed via
the class).

:type value: list of instances of classes derived from
:class:`AbstractQueryParameter`.

This comment was marked as spam.

This comment was marked as spam.

:param value: new query parameters for the instance.
"""
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