diff --git a/README.rst b/README.rst index 12e8602e..c7bba2c4 100644 --- a/README.rst +++ b/README.rst @@ -107,4 +107,4 @@ This library includes enhanced support for GaussDB and openGauss databases: asyncio.run(run()) async-gaussdb is developed and distributed under the Apache 2.0 license -by MagicStack Inc. and the HuaweiCloudDeveloper team. +by MagicStack Inc. and the HuaweiCloudDeveloper team. \ No newline at end of file diff --git a/async_gaussdb/gaussdbproto/types.py b/async_gaussdb/gaussdbproto/types.py index 9ed0e9be..e483f6ec 100644 --- a/async_gaussdb/gaussdbproto/types.py +++ b/async_gaussdb/gaussdbproto/types.py @@ -208,7 +208,7 @@ def from_int(cls: typing.Type[_BitString], x: int, length: int, def __repr__(self) -> str: return ''.format(self.as_string()) - __str__: typing.Callable[['BitString'], str] = __repr__ + __str__ = __repr__ def __eq__(self, other: object) -> bool: if not isinstance(other, BitString): diff --git a/examples/demo.py b/examples/demo.py new file mode 100644 index 00000000..6455292d --- /dev/null +++ b/examples/demo.py @@ -0,0 +1,95 @@ + +import asyncio +import async_gaussdb +# ----------------------------------------------------------------------------- +# Database Connection Configuration +# ----------------------------------------------------------------------------- +DB_CONFIG = { + 'user': 'root', + 'password': 'password', # Replace with your actual password + 'database': 'postgres', + 'host': '127.0.0.1', + 'port': 8000 +} + +async def main(): + print(f"Connecting to GaussDB at {DB_CONFIG['host']}:{DB_CONFIG['port']}...") + + # 1. Establish Connection + # async_gaussdb automatically handles openGauss/GaussDB specific protocols (e.g., SHA256 auth) + conn = await async_gaussdb.connect(**DB_CONFIG) + print("✅ Connection established successfully!") + + try: + # --------------------------------------------------------------------- + # Step 1: Clean up old data (Drop Table) + # --------------------------------------------------------------------- + drop_table_sql = "DROP TABLE IF EXISTS test" + print(f"\n[Executing] {drop_table_sql}") + await conn.execute(drop_table_sql) + print(" -> Table 'test' dropped.") + + # --------------------------------------------------------------------- + # Step 2: Create new table (Create Table) + # --------------------------------------------------------------------- + create_table_sql = ( + "CREATE TABLE test (id serial PRIMARY KEY, num integer, data text)" + ) + print(f"\n[Executing] {create_table_sql}") + await conn.execute(create_table_sql) + print(" -> Table 'test' created.") + + # --------------------------------------------------------------------- + # Step 3: Insert Data + # Note: Async drivers for Postgres/GaussDB typically use $1, $2 placeholders + # instead of %s used in standard synchronous drivers. + # --------------------------------------------------------------------- + insert_data_sql = "INSERT INTO test (num, data) VALUES ($1, $2)" + + # Preparing sample data + data_to_insert = [ + (1, 'initial_data'), + (2, 'data_to_be_updated'), # This row (num=2) will be updated later + (3, 'other_data') + ] + + print(f"\n[Executing] {insert_data_sql}") + for num, data in data_to_insert: + await conn.execute(insert_data_sql, num, data) + print(f" -> Inserted row: num={num}, data='{data}'") + + # --------------------------------------------------------------------- + # Step 4: Update Data + # --------------------------------------------------------------------- + update_data_sql = "UPDATE test SET data = 'gaussdb' WHERE num = 2" + print(f"\n[Executing] {update_data_sql}") + result = await conn.execute(update_data_sql) + # 'result' usually contains the command tag (e.g., "UPDATE 1") + print(f" -> Update complete: {result}") + + # --------------------------------------------------------------------- + # Step 5: Select and Verify Data + # --------------------------------------------------------------------- + select_sql = "SELECT * FROM test ORDER BY id" + print(f"\n[Executing] {select_sql}") + + # fetch() returns a list of Record objects + rows = await conn.fetch(select_sql) + + print("\n--- Query Results ---") + for row in rows: + # Access data by column name or index + print(f"ID: {row['id']} | Num: {row['num']} | Data: {row['data']}") + + except Exception as e: + print(f"\n❌ An error occurred: {e}") + finally: + # --------------------------------------------------------------------- + # Close Connection + # --------------------------------------------------------------------- + print("\nClosing connection...") + await conn.close() + print("✅ Connection closed.") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/ssl_demo.py b/examples/ssl_demo.py new file mode 100644 index 00000000..7025e19e --- /dev/null +++ b/examples/ssl_demo.py @@ -0,0 +1,99 @@ + +import asyncio +import async_gaussdb +import os + +# ----------------------------------------------------------------------------- +# Scenario: Configuring SSL using DSN (Data Source Name) +# This method is simpler than creating a raw ssl.SSLContext and mimics +# standard PostgreSQL connection strings. +# ----------------------------------------------------------------------------- + +# Path to your server's CA certificate. +# If 'sslrootcert' is not provided, the driver defaults to looking at: +# ~/.postgresql/root.crt (Linux/Mac) or %APPDATA%\postgresql\root.crt (Windows) +CERTS = os.path.join(os.path.dirname(__file__), '../tests/certs') +SSL_CERT_FILE = os.path.join(CERTS, 'server.cert.pem') +async def main(): + # ------------------------------------------------------------------------- + # Constructing the DSN Connection String + # Format: gaussdb://user:password@host:port/database?param=value + # + # Key Parameters: + # 1. sslmode=verify-ca -> Verifies the server's certificate signature. + # (Use 'verify-full' to also verify the hostname) + # 2. sslrootcert=... -> Explicitly tells the driver where the CA file is. + # ------------------------------------------------------------------------- + + dsn = ( + f"gaussdb://testuser:Test%40123@127.0.0.1:5432/postgres" + f"?sslmode=verify-ca&sslrootcert={SSL_CERT_FILE}" + ) + + print(f"Connecting via DSN: ...sslmode=verify-ca&sslrootcert={os.path.basename(SSL_CERT_FILE)}") + + try: + # Connect to the database + # We do not need to pass a 'ssl=' context object here because the DSN + # contains all the necessary configuration. + conn = await async_gaussdb.connect(dsn) + + print("SSL Connection Successful (via sslmode)!") + print(f" Encryption Status: {conn._protocol.is_ssl}") + + # --------------------------------------------------------------------- + # Core Tasks (Drop -> Create -> Insert -> Update -> Select) + # --------------------------------------------------------------------- + + # 1. Clean up old data + drop_table_sql = "DROP TABLE IF EXISTS test" + print(f"\n[Executing] {drop_table_sql}") + await conn.execute(drop_table_sql) + + # 2. Create new table + create_table_sql = ( + "CREATE TABLE test (id serial PRIMARY KEY, num integer, data text)" + ) + print(f"\n[Executing] {create_table_sql}") + await conn.execute(create_table_sql) + + # 3. Insert Data (Using $1, $2 placeholders for async driver) + insert_data_sql = "INSERT INTO test (num, data) VALUES ($1, $2)" + print(f"\n[Executing] {insert_data_sql}") + + await conn.execute(insert_data_sql, 1, "sslmode_demo") + await conn.execute(insert_data_sql, 2, "wait_for_update") # num=2 will be updated + print(" -> Inserted 2 rows.") + + # 4. Update Data + update_data_sql = "UPDATE test SET data = 'gaussdb' WHERE num = 2" + print(f"\n[Executing] {update_data_sql}") + await conn.execute(update_data_sql) + print(" -> Update complete.") + + # 5. Select and Verify + select_sql = "SELECT * FROM test ORDER BY id" + print(f"\n[Executing] {select_sql}") + rows = await conn.fetch(select_sql) + + print("\n--- Query Results ---") + for row in rows: + print(f"ID: {row['id']} | Num: {row['num']} | Data: {row['data']}") + + except Exception as e: + print(f"\nERROR Connection or Execution Failed: {e}") + print(" Hint: Check if 'server.crt' exists and if the server supports SSL.") + + finally: + if 'conn' in locals(): + print("\nClosing connection...") + await conn.close() + print("✅ Connection closed.") + +if __name__ == "__main__": + # Check for file existence just for this tutorial to be helpful + if not os.path.exists(SSL_CERT_FILE): + print(f"⚠️ WARNING: The certificate file was not found at: {SSL_CERT_FILE}") + print(" (The code will attempt to connect, but will likely fail)") + + asyncio.run(main()) diff --git a/tests/test_pool.py b/tests/test_pool.py index 1332e4db..9a30353f 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -721,14 +721,14 @@ async def test_pool_max_inactive_time_05(self): # the max inactive lifetime. async with self.create_pool( database='postgres', min_size=2, max_size=2, - max_inactive_connection_lifetime=0.3) as pool: + max_inactive_connection_lifetime=0.5) as pool: - await asyncio.sleep(0.02) + await asyncio.sleep(0.1) self.assertIsNotNone(pool._holders[0]._con) self.assertIsNotNone(pool._holders[1]._con) - await pool.execute('SELECT pg_sleep(0.3)') - await asyncio.sleep(0.3) + await pool.execute('SELECT pg_sleep(0.5)') + await asyncio.sleep(0.5) self.assertIs(pool._holders[0]._con, None) # The connection in the second holder was never used,