Use before implementing pgdbm patterns to avoid common mistakes - provides rationalization table and red flags that prevent pool multiplication, schema errors, and template syntax violations
Provides a rationalization table and red flags to prevent common pgdbm mistakes like pool multiplication, schema errors, and template violations. Use before implementing pgdbm patterns to validate your approach against known anti-patterns.
/plugin marketplace add juanre/pgdbm/plugin install pgdbm@juanre-ai-toolsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Core Principle: Most pgdbm mistakes come from fighting the library's design instead of using it.
This skill provides explicit counters for common rationalizations that lead to bugs.
Violating these = your code is wrong:
| Excuse | Reality | Fix |
|---|---|---|
| "Each service needs different pool sizes" | Shared pool allocates dynamically. Pre-sizing is guessing. | ONE pool with total max |
| "Separate pools give better isolation" | Schema isolation is enough. Separate pools waste connections. | Schema-isolated managers |
| "It's simpler to just write the schema name" | Breaks portability. Code only works in one deployment mode. | Use {{tables.}} always |
| "I'll use search_path instead of templates" | Doesn't work with shared pools. Leads to race conditions. | Use {{tables.}} syntax |
| "module_name seems optional" | Causes migration conflicts when multiple modules share DB. | Always specify unique name |
| "I can switch schema at runtime for tenants" | Race conditions. Same manager used by concurrent requests. | Create manager per schema |
| "I'll close the db_manager in my library" | Closes parent app's pool. Crashes everything. | Check _external_db flag |
| "{{tables.}} is too verbose, I'll skip it" | Works until you use shared pools or change schemas. Then breaks. | Use always, no exceptions |
If you're about to do ANY of these, you're making a mistake:
# WRONG
service1_db = AsyncDatabaseManager(DatabaseConfig(connection_string="postgresql://localhost/app"))
service2_db = AsyncDatabaseManager(DatabaseConfig(connection_string="postgresql://localhost/app"))
What happens:
"⚠️ Creating another connection pool to..."Fix:
# CORRECT
pool = await AsyncDatabaseManager.create_shared_pool(config)
service1_db = AsyncDatabaseManager(pool=pool, schema="service1")
service2_db = AsyncDatabaseManager(pool=pool, schema="service2")
# WRONG
await db.execute('INSERT INTO "myschema".users (email) VALUES ($1)', email)
await db.execute('INSERT INTO users (email) VALUES ($1)', email)
What happens:
Fix:
# CORRECT
await db.execute('INSERT INTO {{tables.users}} (email) VALUES ($1)', email)
# WRONG
migrations = AsyncMigrationManager(db, "migrations")
# Uses "default" module name - conflicts with other modules!
What happens:
Fix:
# CORRECT
migrations = AsyncMigrationManager(db, "migrations", module_name="myservice")
# WRONG
migrations = AsyncMigrationManager(
db,
"migrations",
schema="myschema" # This parameter doesn't exist!
)
What happens:
Fix:
# CORRECT - schema comes from db
db = AsyncDatabaseManager(pool=pool, schema="myschema")
migrations = AsyncMigrationManager(db, "migrations", module_name="myservice")
# WRONG
db = AsyncDatabaseManager(pool=pool, schema="tenant1")
# Later...
db.schema = "tenant2" # Don't do this!
await db.execute("INSERT INTO {{tables.data}} ...")
What happens:
Fix:
# CORRECT - create manager per schema
tenant1_db = AsyncDatabaseManager(pool=pool, schema="tenant1")
tenant2_db = AsyncDatabaseManager(pool=pool, schema="tenant2")
# WRONG
db = AsyncDatabaseManager(pool=shared_pool, schema="myservice")
await db.connect() # ERROR!
What happens:
Fix:
# CORRECT - don't call connect() when using external pool
db = AsyncDatabaseManager(pool=shared_pool, schema="myservice")
# Just use it - no connect() needed
# WRONG in library
class MyLibrary:
async def close(self):
# Always disconnects, even if didn't create connection
await self.db.disconnect()
What happens:
Fix:
# CORRECT - conditional cleanup
class MyLibrary:
async def close(self):
if self.db and not self._external_db:
await self.db.disconnect()
# WRONG - inconsistent
await db.execute('CREATE TABLE {{tables.users}} (...)')
await db.execute('INSERT INTO users (email) VALUES ($1)', email)
What happens:
Fix:
# CORRECT - use templates everywhere
await db.execute('CREATE TABLE {{tables.users}} (...)')
await db.execute('INSERT INTO {{tables.users}} (email) VALUES ($1)', email)
# WRONG
migrations = AsyncMigrationManager(db1, "migrations", module_name="mylib")
migrations = AsyncMigrationManager(db2, "migrations", module_name="mylib")
# Both use same module_name but different schemas!
What happens:
Fix:
# CORRECT - include schema in module_name
migrations = AsyncMigrationManager(db1, "migrations", module_name=f"mylib_{schema1}")
migrations = AsyncMigrationManager(db2, "migrations", module_name=f"mylib_{schema2}")
Possible causes:
{{tables.}} syntaxDebug checklist:
# Check schema configuration
print(f"Configured schema: {db.schema}") # Should match where tables are
# Debug template expansion
print(db.prepare_query("SELECT * FROM {{tables.users}}"))
# Shows: 'SELECT * FROM "myschema".users' or 'SELECT * FROM users'
# Check query uses templates
query = "SELECT * FROM {{tables.users}}" # ✅
query = "SELECT * FROM users" # ❌
# Verify schema exists
schemas = await db.fetch_all(
"SELECT schema_name FROM information_schema.schemata"
)
print([s["schema_name"] for s in schemas])
# Check migrations ran
applied = await migrations.get_applied_migrations()
print(f"Applied migrations: {applied}")
Possible causes:
Debug checklist:
# Check for multiple pools
# Look for this warning in logs:
"⚠️ Creating another connection pool to..."
# Check pool stats
stats = await pool.get_pool_stats()
print(f"Used: {stats['used_size']}/{stats['size']}")
# Verify cleanup in shutdown
# Make sure you have:
await pool.close() # Or await db.disconnect()
Possible causes:
module_nameDebug checklist:
# Check module_name is unique
migrations = AsyncMigrationManager(
db,
"migrations",
module_name="myservice" # Should be unique per service/schema
)
# For dual-mode libraries
module_name = f"mylib_{schema}" # Include schema in name
Run through this before implementing pgdbm:
AsyncDatabaseManager(DatabaseConfig(...)) to same database?{{tables.tablename}} in ALL queries and migrations?module_name for each service/schema?If you answered YES to first question or NO to any others: Review the pattern skills.
Quick self-test: What's wrong with each?
# 1. What's wrong?
db1 = AsyncDatabaseManager(DatabaseConfig(connection_string="postgresql://localhost/app"))
db2 = AsyncDatabaseManager(DatabaseConfig(connection_string="postgresql://localhost/app"))
# 2. What's wrong?
await db.execute("INSERT INTO users (email) VALUES ($1)", email)
# 3. What's wrong?
migrations = AsyncMigrationManager(db, "migrations")
# 4. What's wrong?
db = AsyncDatabaseManager(pool=pool, schema="service1")
await db.connect()
# 5. What's wrong?
db.schema = "different_schema"
Answers:
create_shared_pool(){{tables.users}}module_name="myservice"If pgdbm is fighting you, you're using it wrong.
The library is designed for specific patterns:
Follow these patterns and pgdbm works smoothly. Fight them and you get errors, warnings, and bugs.
pgdbm:using-pgdbmpgdbm:choosing-patternpgdbm:shared-pool-pattern, pgdbm:dual-mode-library