This is an automated email from the ASF dual-hosted git repository.
haonan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new e3fe470a0f [IOTDB-3460] Python API: Add support for DBAPI (#6246)
e3fe470a0f is described below
commit e3fe470a0f1b5a1b4d2c33745e012957c4c7a0e8
Author: Leiyang <[email protected]>
AuthorDate: Mon Jun 13 13:30:35 2022 +0800
[IOTDB-3460] Python API: Add support for DBAPI (#6246)
---
client-py/README.md | 54 ++++
client-py/iotdb/dbapi/Connection.py | 91 +++++++
client-py/iotdb/dbapi/Cursor.py | 282 +++++++++++++++++++++
client-py/iotdb/dbapi/Exceptions.py | 61 +++++
client-py/iotdb/dbapi/__init__.py | 26 ++
client-py/iotdb/dbapi/tests/__init__.py | 17 ++
client-py/iotdb/dbapi/tests/test_connection.py | 57 +++++
client-py/iotdb/dbapi/tests/test_cursor.py | 123 +++++++++
.../UserGuide/API/Programming-Python-Native-API.md | 53 ++++
.../UserGuide/API/Programming-Python-Native-API.md | 52 ++++
10 files changed, 816 insertions(+)
diff --git a/client-py/README.md b/client-py/README.md
index e632e400b3..590bed9c1f 100644
--- a/client-py/README.md
+++ b/client-py/README.md
@@ -353,6 +353,60 @@ class MyTestCase(unittest.TestCase):
by default it will load the image `apache/iotdb:latest`, if you want a
specific version just pass it like e.g. `IoTDBContainer("apache/iotdb:0.12.0")`
to get version `0.12.0` running.
+### IoTDB DBAPI
+
+IoTDB DBAPI implements the Python DB API 2.0 specification
(https://peps.python.org/pep-0249/), which defines a common
+interface for accessing databases in Python.
+
+#### Examples
++ Initialization
+
+The initialized parameters are consistent with the session part (except for
the sqlalchemy_mode).
+```python
+from iotdb.dbapi import connect
+
+ip = "127.0.0.1"
+port_ = "6667"
+username_ = "root"
+password_ = "root"
+conn = connect(ip, port_, username_,
password_,fetch_size=1024,zone_id="UTC+8",sqlalchemy_mode=False)
+cursor = conn.cursor()
+```
++ simple SQL statement execution
+```python
+cursor.execute("SELECT * FROM root.*")
+for row in cursor.fetchall():
+ print(row)
+```
+
++ execute SQL with parameter
+
+IoTDB DBAPI supports pyformat style parameters
+```python
+cursor.execute("SELECT * FROM root.* WHERE time <
%(time)s",{"time":"2017-11-01T00:08:00.000"})
+for row in cursor.fetchall():
+ print(row)
+```
+
++ execute SQL with parameter sequences
+```python
+seq_of_parameters = [
+ {"timestamp": 1, "temperature": 1},
+ {"timestamp": 2, "temperature": 2},
+ {"timestamp": 3, "temperature": 3},
+ {"timestamp": 4, "temperature": 4},
+ {"timestamp": 5, "temperature": 5},
+]
+sql = "insert into root.cursor(timestamp,temperature)
values(%(timestamp)s,%(temperature)s)"
+cursor.executemany(sql,seq_of_parameters)
+```
+
++ close the connection and cursor
+```python
+cursor.close()
+conn.close()
+```
+
## Developers
### Introduction
diff --git a/client-py/iotdb/dbapi/Connection.py
b/client-py/iotdb/dbapi/Connection.py
new file mode 100644
index 0000000000..aee5520e9a
--- /dev/null
+++ b/client-py/iotdb/dbapi/Connection.py
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+
+import logging
+
+from iotdb.Session import Session
+
+from .Cursor import Cursor
+from .Exceptions import ConnectionError, ProgrammingError
+
+logger = logging.getLogger("IoTDB")
+
+
+class Connection(object):
+ def __init__(
+ self,
+ host,
+ port,
+ username=Session.DEFAULT_USER,
+ password=Session.DEFAULT_PASSWORD,
+ fetch_size=Session.DEFAULT_FETCH_SIZE,
+ zone_id=Session.DEFAULT_ZONE_ID,
+ enable_rpc_compression=False,
+ sqlalchemy_mode=False,
+ ):
+ self.__session = Session(host, port, username, password, fetch_size,
zone_id)
+ self.__sqlalchemy_mode = sqlalchemy_mode
+ self.__is_close = True
+ try:
+ self.__session.open(enable_rpc_compression)
+ self.__is_close = False
+ except Exception as e:
+ raise ConnectionError(e)
+
+ def close(self):
+ """
+ Close the connection now
+ """
+ if self.__is_close:
+ return
+ self.__session.close()
+ self.__is_close = True
+
+ def cursor(self):
+ """
+ Return a new Cursor Object using the connection.
+ """
+ if not self.__is_close:
+ return Cursor(self, self.__session, self.__sqlalchemy_mode)
+ else:
+ raise ProgrammingError("Connection closed")
+
+ def commit(self):
+ """
+ Not supported method.
+ """
+ pass
+
+ def rollback(self):
+ """
+ Not supported method.
+ """
+ pass
+
+ @property
+ def is_close(self):
+ """
+ This read-only attribute specified whether the object is closed
+ """
+ return self.__is_close
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
diff --git a/client-py/iotdb/dbapi/Cursor.py b/client-py/iotdb/dbapi/Cursor.py
new file mode 100644
index 0000000000..fd680835ba
--- /dev/null
+++ b/client-py/iotdb/dbapi/Cursor.py
@@ -0,0 +1,282 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+
+import logging
+import warnings
+
+from iotdb.Session import Session
+
+from .Exceptions import ProgrammingError
+
+logger = logging.getLogger("IoTDB")
+
+
+class Cursor(object):
+ def __init__(self, connection, session: Session, sqlalchemy_mode):
+ self.__connection = connection
+ self.__session = session
+ self.__sqlalchemy_mode = sqlalchemy_mode
+ self.__arraysize = 1
+ self.__is_close = False
+ self.__result = None
+ self.__rows = None
+ self.__rowcount = -1
+
+ @property
+ def description(self):
+ """
+ This read-only attribute is a sequence of 7-item sequences.
+ """
+ if self.__is_close or not self.__result["col_names"]:
+ return
+
+ description = []
+
+ col_names = self.__result["col_names"]
+ col_types = self.__result["col_types"]
+
+ for i in range(len(col_names)):
+ description.append(
+ (
+ col_names[i],
+ None if self.__sqlalchemy_mode is True else
col_types[i].value,
+ None,
+ None,
+ None,
+ None,
+ col_names[i] == "Time",
+ )
+ )
+ return tuple(description)
+
+ @property
+ def arraysize(self):
+ """
+ This read/write attribute specifies the number of rows to fetch at a
time with .fetchmany().
+ """
+ return self.__arraysize
+
+ @arraysize.setter
+ def arraysize(self, value):
+ """
+ Set the arraysize.
+ :param value: arraysize
+ """
+ try:
+ self.__arraysize = int(value)
+ except TypeError:
+ self.__arraysize = 1
+
+ @property
+ def rowcount(self):
+ """
+ This read-only attribute specifies the number of rows that the last
+ .execute*() produced (for DQL statements like ``SELECT``) or affected
+ (for DML statements like ``DELETE`` or ``INSERT`` return 0 if
successful, -1 if unsuccessful).
+ """
+ if self.__is_close or self.__result is None or "row_count" not in
self.__result:
+ return -1
+ return self.__result.get("row_count", -1)
+
+ def execute(self, operation, parameters=None):
+ """
+ Prepare and execute a database operation (query or command).
+ :param operation: a database operation
+ :param parameters: parameters of the operation
+ """
+ if self.__connection.is_close:
+ raise ProgrammingError("Connection closed!")
+
+ if self.__is_close:
+ raise ProgrammingError("Cursor closed!")
+
+ if parameters is None:
+ sql = operation
+ else:
+ sql = operation % parameters
+
+ time_index = []
+ if self.__sqlalchemy_mode:
+ sql_seqs = []
+ seqs = sql.split("\n")
+ for seq in seqs:
+ if seq.find("FROM Time Index") >= 0:
+ time_index = [
+ int(index)
+ for index in seq.replace("FROM Time Index", "").split()
+ ]
+ else:
+ sql_seqs.append(seq)
+ sql = "\n".join(sql_seqs)
+
+ try:
+ data_set = self.__session.execute_statement(sql)
+ col_names = None
+ col_types = None
+ rows = []
+
+ if data_set:
+ data = data_set.todf()
+
+ if self.__sqlalchemy_mode and time_index:
+ time_column = data.columns[0]
+ time_column_value = data.Time
+ del data[time_column]
+ for index in time_index:
+ data.insert(index, time_column + str(index),
time_column_value)
+
+ col_names = data.columns.tolist()
+ col_types = data_set.get_column_types()
+ rows = data.values.tolist()
+ data_set.close_operation_handle()
+
+ self.__result = {
+ "col_names": col_names,
+ "col_types": col_types,
+ "rows": rows,
+ "row_count": len(rows),
+ }
+ except Exception:
+ self.__result = {
+ "col_names": None,
+ "col_types": None,
+ "rows": [],
+ "row_count": -1,
+ }
+ self.__rows = iter(self.__result["rows"])
+
+ def executemany(self, operation, seq_of_parameters=None):
+ """
+ Prepare a database operation (query or command) and then execute it
+ against all parameter sequences or mappings found in the sequence
+ ``seq_of_parameters``
+ :param operation: a database operation
+ :param seq_of_parameters: pyformat style parameter list of the
operation
+ """
+ if self.__connection.is_close:
+ raise ProgrammingError("Connection closed!")
+
+ if self.__is_close:
+ raise ProgrammingError("Cursor closed!")
+
+ rows = []
+ if seq_of_parameters is None:
+ self.execute(operation)
+ rows.extend(self.__result["rows"])
+ else:
+ for parameters in seq_of_parameters:
+ self.execute(operation, parameters)
+ rows.extend(self.__result["rows"])
+
+ self.__result["rows"] = rows
+ self.__rows = iter(self.__result["rows"])
+
+ def fetchone(self):
+ """
+ Fetch the next row of a query result set, returning a single sequence,
+ or None when no more data is available.
+ Alias for ``next()``.
+ """
+ try:
+ return self.next()
+ except StopIteration:
+ return None
+
+ def fetchmany(self, count=None):
+ """
+ Fetch the next set of rows of a query result, returning a sequence of
+ sequences (e.g. a list of tuples). An empty sequence is returned when
+ no more rows are available.
+ """
+ if count is None:
+ count = self.__arraysize
+ if count == 0:
+ return self.fetchall()
+ result = []
+ for i in range(count):
+ try:
+ result.append(self.next())
+ except StopIteration:
+ pass
+ return result
+
+ def fetchall(self):
+ """
+ Fetch all (remaining) rows of a query result, returning them as a
+ sequence of sequences (e.g. a list of tuples). Note that the cursor's
+ arraysize attribute can affect the performance of this operation.
+ """
+ result = []
+ iterate = True
+ while iterate:
+ try:
+ result.append(self.next())
+ except StopIteration:
+ iterate = False
+ return result
+
+ def next(self):
+ """
+ Return the next row of a query result set, respecting if cursor was
+ closed.
+ """
+ if self.__result is None:
+ raise ProgrammingError(
+ "No result available. execute() or executemany() must be
called first."
+ )
+ elif not self.__is_close:
+ return next(self.__rows)
+ else:
+ raise ProgrammingError("Cursor closed!")
+
+ __next__ = next
+
+ def close(self):
+ """
+ Close the cursor now.
+ """
+ self.__is_close = True
+ self.__result = None
+
+ def setinputsizes(self, sizes):
+ """
+ Not supported method.
+ """
+ pass
+
+ def setoutputsize(self, size, column=None):
+ """
+ Not supported method.
+ """
+ pass
+
+ def __iter__(self):
+ """
+ Support iterator interface:
+ http://legacy.python.org/dev/peps/pep-0249/#iter
+ This iterator is shared. Advancing this iterator will advance other
+ iterators created from this cursor.
+ """
+ warnings.warn("DB-API extension cursor.__iter__() used")
+ return self
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
diff --git a/client-py/iotdb/dbapi/Exceptions.py
b/client-py/iotdb/dbapi/Exceptions.py
new file mode 100644
index 0000000000..d58689c869
--- /dev/null
+++ b/client-py/iotdb/dbapi/Exceptions.py
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+
+
+class Error(Exception):
+ pass
+
+
+class Warning(Exception):
+ pass
+
+
+class DatabaseError(Error):
+ pass
+
+
+class DataError(DatabaseError):
+ pass
+
+
+class InterfaceError(Error):
+ pass
+
+
+class InternalError(DatabaseError):
+ pass
+
+
+class IntegrityError(DatabaseError):
+ pass
+
+
+class OperationalError(DatabaseError):
+ pass
+
+
+class ProgrammingError(DatabaseError):
+ pass
+
+
+class NotSupportedError(DatabaseError):
+ pass
+
+
+class ConnectionError(DatabaseError):
+ pass
diff --git a/client-py/iotdb/dbapi/__init__.py
b/client-py/iotdb/dbapi/__init__.py
new file mode 100644
index 0000000000..9f80061750
--- /dev/null
+++ b/client-py/iotdb/dbapi/__init__.py
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 .Connection import Connection as connect
+from .Exceptions import Error
+
+__all__ = [connect, Error]
+
+apilevel = "2.0"
+threadsafety = 2
+paramstyle = "pyformat"
diff --git a/client-py/iotdb/dbapi/tests/__init__.py
b/client-py/iotdb/dbapi/tests/__init__.py
new file mode 100644
index 0000000000..2a1e720805
--- /dev/null
+++ b/client-py/iotdb/dbapi/tests/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
diff --git a/client-py/iotdb/dbapi/tests/test_connection.py
b/client-py/iotdb/dbapi/tests/test_connection.py
new file mode 100644
index 0000000000..cb1f6c1e65
--- /dev/null
+++ b/client-py/iotdb/dbapi/tests/test_connection.py
@@ -0,0 +1,57 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 iotdb.IoTDBContainer import IoTDBContainer
+from iotdb.dbapi import connect
+
+final_flag = True
+failed_count = 0
+
+
+def test_fail():
+ global failed_count
+ global final_flag
+ final_flag = False
+ failed_count += 1
+
+
+def print_message(message):
+ print("*********")
+ print(message)
+ print("*********")
+
+
+def test_connection():
+ with IoTDBContainer("iotdb:dev") as db:
+ db: IoTDBContainer
+ conn = connect(db.get_container_host_ip(), db.get_exposed_port(6667))
+ if conn.is_close:
+ print("can't create connect")
+ exit(1)
+ conn.close()
+ if not conn.is_close:
+ test_fail()
+ print_message("failed to close the connection!")
+
+
+if final_flag:
+ print("All executions done!!")
+else:
+ print("Some test failed, please have a check")
+ print("failed count: ", failed_count)
+ exit(1)
diff --git a/client-py/iotdb/dbapi/tests/test_cursor.py
b/client-py/iotdb/dbapi/tests/test_cursor.py
new file mode 100644
index 0000000000..6cd42257dc
--- /dev/null
+++ b/client-py/iotdb/dbapi/tests/test_cursor.py
@@ -0,0 +1,123 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 iotdb.IoTDBContainer import IoTDBContainer
+from iotdb.dbapi import connect
+from iotdb.dbapi.Cursor import Cursor
+
+final_flag = True
+failed_count = 0
+
+
+def test_fail():
+ global failed_count
+ global final_flag
+ final_flag = False
+ failed_count += 1
+
+
+def print_message(message):
+ print("*********")
+ print(message)
+ print("*********")
+
+
+def test_cursor():
+ with IoTDBContainer("iotdb:dev") as db:
+ db: IoTDBContainer
+ conn = connect(db.get_container_host_ip(), db.get_exposed_port(6667))
+ cursor: Cursor = conn.cursor()
+
+ # execute test
+ cursor.execute("create storage group root.cursor")
+ cursor.execute("create storage group root.cursor_s1")
+ cursor.execute("delete storage group root.cursor_s1")
+ if cursor.rowcount < 0:
+ test_fail()
+ print_message("execute test failed!")
+
+ # execute with args test
+ cursor.execute(
+ "create timeseries root.cursor.temperature with
datatype=FLOAT,encoding=RLE"
+ )
+ cursor.execute(
+ "insert into root.cursor(timestamp,temperature)
values(1,%(temperature)s)",
+ {"temperature": 0.3},
+ )
+ cursor.execute(
+ "insert into root.cursor(timestamp,temperature)
values(2,%(temperature)s)",
+ {"temperature": 0.4},
+ )
+ cursor.execute("select * from root.cursor")
+ count = 2
+ actual_count = 0
+ for row in cursor.fetchall():
+ actual_count += 1
+ if count != actual_count:
+ test_fail()
+ print_message("execute with args test failed!")
+
+ # executemany with args test
+ args = [
+ {"timestamp": 3, "temperature": 3},
+ {"timestamp": 4, "temperature": 4},
+ {"timestamp": 5, "temperature": 5},
+ {"timestamp": 6, "temperature": 6},
+ {"timestamp": 7, "temperature": 7},
+ ]
+ cursor.executemany(
+ "insert into root.cursor(timestamp,temperature)
values(%(timestamp)s,%(temperature)s)",
+ args,
+ )
+ cursor.execute("select * from root.cursor")
+ count = 7
+ actual_count = 0
+ for row in cursor.fetchall():
+ actual_count += 1
+ if count != actual_count:
+ test_fail()
+ print_message("executemany with args test failed!")
+
+ # fetchmany test
+ cursor.execute("select * from root.cursor")
+ count = 2
+ actual_count = 0
+ for row in cursor.fetchmany(count):
+ actual_count += 1
+ if count != actual_count:
+ test_fail()
+ print_message("fetchmany test failed!")
+
+ # fetchone test
+ cursor.execute("select * from root.cursor")
+ row = cursor.fetchone()
+ if row[0] != 1:
+ test_fail()
+ print_message("fetchone test failed")
+
+ cursor.execute("delete storage group root.cursor")
+ cursor.close()
+ conn.close()
+
+
+if final_flag:
+ print("All executions done!!")
+else:
+ print("Some test failed, please have a check")
+ print("failed count: ", failed_count)
+ exit(1)
diff --git a/docs/UserGuide/API/Programming-Python-Native-API.md
b/docs/UserGuide/API/Programming-Python-Native-API.md
index 30799133e2..e3315de5b9 100644
--- a/docs/UserGuide/API/Programming-Python-Native-API.md
+++ b/docs/UserGuide/API/Programming-Python-Native-API.md
@@ -330,6 +330,59 @@ class MyTestCase(unittest.TestCase):
by default it will load the image `apache/iotdb:latest`, if you want a
specific version just pass it like e.g. `IoTDBContainer("apache/iotdb:0.12.0")`
to get version `0.12.0` running.
+### IoTDB DBAPI
+
+IoTDB DBAPI implements the Python DB API 2.0 specification
(https://peps.python.org/pep-0249/), which defines a common
+interface for accessing databases in Python.
+
+#### Examples
++ Initialization
+
+The initialized parameters are consistent with the session part (except for
the sqlalchemy_mode).
+```python
+from iotdb.dbapi import connect
+
+ip = "127.0.0.1"
+port_ = "6667"
+username_ = "root"
+password_ = "root"
+conn = connect(ip, port_, username_,
password_,fetch_size=1024,zone_id="UTC+8",sqlalchemy_mode=False)
+cursor = conn.cursor()
+```
++ simple SQL statement execution
+```python
+cursor.execute("SELECT * FROM root.*")
+for row in cursor.fetchall():
+ print(row)
+```
+
++ execute SQL with parameter
+
+IoTDB DBAPI supports pyformat style parameters
+```python
+cursor.execute("SELECT * FROM root.* WHERE time <
%(time)s",{"time":"2017-11-01T00:08:00.000"})
+for row in cursor.fetchall():
+ print(row)
+```
+
++ execute SQL with parameter sequences
+```python
+seq_of_parameters = [
+ {"timestamp": 1, "temperature": 1},
+ {"timestamp": 2, "temperature": 2},
+ {"timestamp": 3, "temperature": 3},
+ {"timestamp": 4, "temperature": 4},
+ {"timestamp": 5, "temperature": 5},
+]
+sql = "insert into root.cursor(timestamp,temperature)
values(%(timestamp)s,%(temperature)s)"
+cursor.executemany(sql,seq_of_parameters)
+```
+
++ close the connection and cursor
+```python
+cursor.close()
+conn.close()
+```
## Developers
diff --git a/docs/zh/UserGuide/API/Programming-Python-Native-API.md
b/docs/zh/UserGuide/API/Programming-Python-Native-API.md
index aec3c832a1..23e66f3dd0 100644
--- a/docs/zh/UserGuide/API/Programming-Python-Native-API.md
+++ b/docs/zh/UserGuide/API/Programming-Python-Native-API.md
@@ -325,6 +325,58 @@ class MyTestCase(unittest.TestCase):
默认情况下,它会拉取最新的 IoTDB 镜像 `apache/iotdb:latest`进行测试,如果您想指定待测 IoTDB
的版本,您只需要将版本信息像这样声明:`IoTDBContainer("apache/iotdb:0.12.0")`,此时,您就会得到一个`0.12.0`版本的
IoTDB 实例。
+### IoTDB DBAPI
+
+IoTDB DBAPI 遵循 Python DB API 2.0 规范
(https://peps.python.org/pep-0249/),实现了通过Python语言访问数据库的通用接口。
+
+#### 例子
++ 初始化
+
+初始化的参数与Session部分保持一致(sqlalchemy_mode参数除外,该参数仅在SQLAlchemy方言中使用)
+```python
+from iotdb.dbapi import connect
+
+ip = "127.0.0.1"
+port_ = "6667"
+username_ = "root"
+password_ = "root"
+conn = connect(ip, port_, username_,
password_,fetch_size=1024,zone_id="UTC+8",sqlalchemy_mode=False)
+cursor = conn.cursor()
+```
++ 执行简单的SQL语句
+```python
+cursor.execute("SELECT * FROM root.*")
+for row in cursor.fetchall():
+ print(row)
+```
+
++ 执行带有参数的SQL语句
+
+IoTDB DBAPI 支持pyformat风格的参数
+```python
+cursor.execute("SELECT * FROM root.* WHERE time <
%(time)s",{"time":"2017-11-01T00:08:00.000"})
+for row in cursor.fetchall():
+ print(row)
+```
+
++ 批量执行带有参数的SQL语句
+```python
+seq_of_parameters = [
+ {"timestamp": 1, "temperature": 1},
+ {"timestamp": 2, "temperature": 2},
+ {"timestamp": 3, "temperature": 3},
+ {"timestamp": 4, "temperature": 4},
+ {"timestamp": 5, "temperature": 5},
+]
+sql = "insert into root.cursor(timestamp,temperature)
values(%(timestamp)s,%(temperature)s)"
+cursor.executemany(sql,seq_of_parameters)
+```
+
++ 关闭连接
+```python
+cursor.close()
+conn.close()
+```
## 给开发人员