Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
feat: DB-API driver + unit tests (#160)
* feat: DB-API driver + unit tests * chore: imports in test files rearranged * chore: added coding directive * chore: * chore: encoding directive * chore: skipping Python 2 incompatible tests * chore: skipping Python 2 incompatible tests * chore: skipping Python 2 incompatible tests * chore: skipping Python 2 incompatible tests * chore: lint format * chore: license headers updated * chore: minor fixes Co-authored-by: Chris Kleinknecht <libc@google.com>
- Loading branch information
Showing
20 changed files
with
3,774 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# Copyright 2020 Google LLC All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Connection-based DB API for Cloud Spanner.""" | ||
|
||
from google.cloud.spanner_dbapi.connection import Connection | ||
from google.cloud.spanner_dbapi.connection import connect | ||
|
||
from google.cloud.spanner_dbapi.cursor import Cursor | ||
|
||
from google.cloud.spanner_dbapi.exceptions import DatabaseError | ||
from google.cloud.spanner_dbapi.exceptions import DataError | ||
from google.cloud.spanner_dbapi.exceptions import Error | ||
from google.cloud.spanner_dbapi.exceptions import IntegrityError | ||
from google.cloud.spanner_dbapi.exceptions import InterfaceError | ||
from google.cloud.spanner_dbapi.exceptions import InternalError | ||
from google.cloud.spanner_dbapi.exceptions import NotSupportedError | ||
from google.cloud.spanner_dbapi.exceptions import OperationalError | ||
from google.cloud.spanner_dbapi.exceptions import ProgrammingError | ||
from google.cloud.spanner_dbapi.exceptions import Warning | ||
|
||
from google.cloud.spanner_dbapi.parse_utils import get_param_types | ||
|
||
from google.cloud.spanner_dbapi.types import BINARY | ||
from google.cloud.spanner_dbapi.types import DATETIME | ||
from google.cloud.spanner_dbapi.types import NUMBER | ||
from google.cloud.spanner_dbapi.types import ROWID | ||
from google.cloud.spanner_dbapi.types import STRING | ||
from google.cloud.spanner_dbapi.types import Binary | ||
from google.cloud.spanner_dbapi.types import Date | ||
from google.cloud.spanner_dbapi.types import DateFromTicks | ||
from google.cloud.spanner_dbapi.types import Time | ||
from google.cloud.spanner_dbapi.types import TimeFromTicks | ||
from google.cloud.spanner_dbapi.types import Timestamp | ||
from google.cloud.spanner_dbapi.types import TimestampStr | ||
from google.cloud.spanner_dbapi.types import TimestampFromTicks | ||
|
||
from google.cloud.spanner_dbapi.version import DEFAULT_USER_AGENT | ||
|
||
apilevel = "2.0" # supports DP-API 2.0 level. | ||
paramstyle = "format" # ANSI C printf format codes, e.g. ...WHERE name=%s. | ||
|
||
# Threads may share the module, but not connections. This is a paranoid threadsafety | ||
# level, but it is necessary for starters to use when debugging failures. | ||
# Eventually once transactions are working properly, we'll update the | ||
# threadsafety level. | ||
threadsafety = 1 | ||
|
||
|
||
__all__ = [ | ||
"Connection", | ||
"connect", | ||
"Cursor", | ||
"DatabaseError", | ||
"DataError", | ||
"Error", | ||
"IntegrityError", | ||
"InterfaceError", | ||
"InternalError", | ||
"NotSupportedError", | ||
"OperationalError", | ||
"ProgrammingError", | ||
"Warning", | ||
"DEFAULT_USER_AGENT", | ||
"apilevel", | ||
"paramstyle", | ||
"threadsafety", | ||
"get_param_types", | ||
"Binary", | ||
"Date", | ||
"DateFromTicks", | ||
"Time", | ||
"TimeFromTicks", | ||
"Timestamp", | ||
"TimestampFromTicks", | ||
"BINARY", | ||
"STRING", | ||
"NUMBER", | ||
"DATETIME", | ||
"ROWID", | ||
"TimestampStr", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
# Copyright 2020 Google LLC All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from google.cloud.spanner_dbapi.parse_utils import get_param_types | ||
from google.cloud.spanner_dbapi.parse_utils import parse_insert | ||
from google.cloud.spanner_dbapi.parse_utils import sql_pyformat_args_to_spanner | ||
from google.cloud.spanner_v1 import param_types | ||
|
||
|
||
SQL_LIST_TABLES = """ | ||
SELECT | ||
t.table_name | ||
FROM | ||
information_schema.tables AS t | ||
WHERE | ||
t.table_catalog = '' and t.table_schema = '' | ||
""" | ||
|
||
SQL_GET_TABLE_COLUMN_SCHEMA = """SELECT | ||
COLUMN_NAME, IS_NULLABLE, SPANNER_TYPE | ||
FROM | ||
INFORMATION_SCHEMA.COLUMNS | ||
WHERE | ||
TABLE_SCHEMA = '' | ||
AND | ||
TABLE_NAME = @table_name | ||
""" | ||
|
||
# This table maps spanner_types to Spanner's data type sizes as per | ||
# https://cloud.google.com/spanner/docs/data-types#allowable-types | ||
# It is used to map `display_size` to a known type for Cursor.description | ||
# after a row fetch. | ||
# Since ResultMetadata | ||
# https://cloud.google.com/spanner/docs/reference/rest/v1/ResultSetMetadata | ||
# does not send back the actual size, we have to lookup the respective size. | ||
# Some fields' sizes are dependent upon the dynamic data hence aren't sent back | ||
# by Cloud Spanner. | ||
code_to_display_size = { | ||
param_types.BOOL.code: 1, | ||
param_types.DATE.code: 4, | ||
param_types.FLOAT64.code: 8, | ||
param_types.INT64.code: 8, | ||
param_types.TIMESTAMP.code: 12, | ||
} | ||
|
||
|
||
def _execute_insert_heterogenous(transaction, sql_params_list): | ||
for sql, params in sql_params_list: | ||
sql, params = sql_pyformat_args_to_spanner(sql, params) | ||
param_types = get_param_types(params) | ||
transaction.execute_update(sql, params=params, param_types=param_types) | ||
|
||
|
||
def _execute_insert_homogenous(transaction, parts): | ||
# Perform an insert in one shot. | ||
table = parts.get("table") | ||
columns = parts.get("columns") | ||
values = parts.get("values") | ||
return transaction.insert(table, columns, values) | ||
|
||
|
||
def handle_insert(connection, sql, params): | ||
parts = parse_insert(sql, params) | ||
|
||
# The split between the two styles exists because: | ||
# in the common case of multiple values being passed | ||
# with simple pyformat arguments, | ||
# SQL: INSERT INTO T (f1, f2) VALUES (%s, %s, %s) | ||
# Params: [(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,)] | ||
# we can take advantage of a single RPC with: | ||
# transaction.insert(table, columns, values) | ||
# instead of invoking: | ||
# with transaction: | ||
# for sql, params in sql_params_list: | ||
# transaction.execute_sql(sql, params, param_types) | ||
# which invokes more RPCs and is more costly. | ||
|
||
if parts.get("homogenous"): | ||
# The common case of multiple values being passed in | ||
# non-complex pyformat args and need to be uploaded in one RPC. | ||
return connection.database.run_in_transaction(_execute_insert_homogenous, parts) | ||
else: | ||
# All the other cases that are esoteric and need | ||
# transaction.execute_sql | ||
sql_params_list = parts.get("sql_params_list") | ||
return connection.database.run_in_transaction( | ||
_execute_insert_heterogenous, sql_params_list | ||
) | ||
|
||
|
||
class ColumnInfo: | ||
"""Row column description object.""" | ||
|
||
def __init__( | ||
self, | ||
name, | ||
type_code, | ||
display_size=None, | ||
internal_size=None, | ||
precision=None, | ||
scale=None, | ||
null_ok=False, | ||
): | ||
self.name = name | ||
self.type_code = type_code | ||
self.display_size = display_size | ||
self.internal_size = internal_size | ||
self.precision = precision | ||
self.scale = scale | ||
self.null_ok = null_ok | ||
|
||
self.fields = ( | ||
self.name, | ||
self.type_code, | ||
self.display_size, | ||
self.internal_size, | ||
self.precision, | ||
self.scale, | ||
self.null_ok, | ||
) | ||
|
||
def __repr__(self): | ||
return self.__str__() | ||
|
||
def __getitem__(self, index): | ||
return self.fields[index] | ||
|
||
def __str__(self): | ||
str_repr = ", ".join( | ||
filter( | ||
lambda part: part is not None, | ||
[ | ||
"name='%s'" % self.name, | ||
"type_code=%d" % self.type_code, | ||
"display_size=%d" % self.display_size | ||
if self.display_size | ||
else None, | ||
"internal_size=%d" % self.internal_size | ||
if self.internal_size | ||
else None, | ||
"precision='%s'" % self.precision if self.precision else None, | ||
"scale='%s'" % self.scale if self.scale else None, | ||
"null_ok='%s'" % self.null_ok if self.null_ok else None, | ||
], | ||
) | ||
) | ||
return "ColumnInfo(%s)" % str_repr |
Oops, something went wrong.