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()
+```
 
 ## 给开发人员
 

Reply via email to