rapidquery 0.1.0a1__cp313-cp313-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,877 @@
1
+ Metadata-Version: 2.4
2
+ Name: rapidquery
3
+ Version: 0.1.0a1
4
+ Classifier: Programming Language :: Python :: Implementation :: CPython
5
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
6
+ Classifier: Programming Language :: Python :: 3 :: Only
7
+ Classifier: Programming Language :: Python :: 3.9
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Rust
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Typing :: Typed
19
+ License-File: LICENSE
20
+ Summary: RapiQuery is the fastest, full-feature, and easy-to-use Python SQL query builder written in Rust.
21
+ Keywords: SQL,dynamic-sql,sql-builder,rapidorm,seaql,pypika,orm,rapidquery
22
+ Home-Page: https://github.com/awolverp/rapidquery
23
+ Author-email: awolverp <awolverp@gmail.com>
24
+ License: GNU GPLv3
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
27
+ Project-URL: Homepage, https://github.com/awolverp/rapidquery
28
+
29
+ # RapidQuery
30
+ __*RapidQuery: High-Performance SQL Query Builder for Python*__
31
+
32
+ RapidQuery is a powerful SQL query builder library designed for Python, combining the simplicity of Python with the raw speed and safety of **Rust**. Build complex SQL queries effortlessly and efficiently, with a library that prioritizes both performance and ease of use.
33
+
34
+ **Key Features:**
35
+ - 🚀 **Blazing Fast Performance**: Leveraging the power of Rust under the hood, RapidQuery ensures your query building process is as fast as possible.
36
+ - 🛡️ **SQL Injection Protection**: Built-in security measures to prevent SQL injection attacks by default.
37
+ - 📝 **Intuitive Pythonic API**: Write clean, readable code with an API that feels natural to Python developers.
38
+ - 🐍 **Seamless Python Integration**: Works perfectly with popular Python web frameworks and database drivers.
39
+
40
+ **Built on Solid Foundations** \
41
+ RapidQuery is built with **Rust** and powered by the robust **SeaQuery** crate, bringing enterprise-grade reliability and performance to your Python applications.
42
+
43
+ **Why RapidQuery Was Created** \
44
+ In a landscape filled with SQL libraries, we noticed a critical gap: **performance was often an afterthought**. That's why we built RapidQuery with speed as our primary and enduring focus.
45
+
46
+ **Our Core Mission:**
47
+ - **Performance First**: While other libraries compromise on speed, we engineered RapidQuery from the ground up for maximum performance.
48
+ - **Foundation for Future ORM**: RapidQuery serves as the foundational layer for building a next-generation, high-performance ORM for Python.
49
+ - **Meeting Python's Needs**: Python dominates backend development, particularly in web applications. Every backend deserves a fast, powerful database interaction layer — that's exactly what we're building.
50
+ - **Security by Design**: Unlike many alternatives, we bake security directly into our architecture with automatic SQL injection prevention.
51
+
52
+ Build your SQL queries faster, safer, and more efficiently than ever before. RapidQuery - where Python meets Rust's performance for database excellence.
53
+
54
+ ## Installation
55
+ To install RapidQuery, run the following command:
56
+ ```bash
57
+ pip3 install rapidquery
58
+ ```
59
+
60
+ > [!NOTE]\
61
+ > RapidQuery requires Python 3.10+. Supports CPython and PyPy.
62
+
63
+ ## Backends
64
+ RapidQuery supports `PostgreSQL`, `MySQL`, and `SQLite` databases. In RapidQuery, these are referred to as `backend`s.
65
+ When building SQL statements, you should specify your target backend.
66
+
67
+ ## Quick Example
68
+ ```python
69
+ import rapidquery as rq
70
+ import datetime
71
+
72
+ stmt = rq.Insert().into("repositories").values(name="RapidQuery", created_at=datetime.datetime.now())
73
+ stmt.to_sql("postgres")
74
+ # INSERT INTO "repositories" ("name", "created_at") VALUES ('RapidQuery', '2025-11-14 16:18:59.188940')
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ 1. Core Concepts
80
+ 1. [**AdaptedValue**](#adaptedvalue)
81
+ 2. [**Expr**](#expr)
82
+ 3. [**Statement Builders**](#statement-builders)
83
+ 2. Query Statements
84
+ 1. [**Query Select**](#query-select)
85
+ 2. [**Query Insert**](#query-insert)
86
+ 3. [**Query Update**](#query-update)
87
+ 4. [**Query Delete**](#query-delete)
88
+ 3. More About Queries
89
+ 1. [**Custom Function**](#custom-functions)
90
+ 4. Schema Statements
91
+ 1. [**Table Create**](#table-create)
92
+ 2. [**Table Alter**](#table-alter)
93
+ 3. [**Table Drop**](#table-drop)
94
+ 4. [**Table Rename**](#table-rename)
95
+ 5. [**Table Truncate**](#table-truncate)
96
+ 8. [**Index Create**](#index-create)
97
+ 9. [**Index Drop**](#index-drop)
98
+ 5. Advanced Usage
99
+ 1. [**ORM-like**](#orm-like)
100
+ 2. [**Table Alias**](#table-alias)
101
+ 6. Performance
102
+ 1. [**Benchmarks**](#benchmarks)
103
+ 2. [**Performance Tips**](#performance-tips)
104
+
105
+ ### Core Concepts
106
+ #### AdaptedValue
107
+ `AdaptedValue` bridges Python types, Rust types, and SQL types for seamless data conversion.
108
+
109
+ This class handles validation, adaptation, and conversion between different
110
+ type systems used in the application stack.
111
+
112
+ ```python
113
+ import rapidquery as rq
114
+
115
+ # Let the system detect types automatically
116
+ rq.AdaptedValue(1) # -> INTEGER SQL type
117
+ rq.AdaptedValue(1.4) # -> DOUBLE SQL type
118
+ rq.AdaptedValue("127.0.0.1") # -> VARCHAR SQL type
119
+ rq.AdaptedValue({"key": "value"}) # -> JSON SQL type
120
+
121
+ # Explicitly specify the type
122
+ rq.AdaptedValue(1, rq.TinyUnsignedType()) # -> TINYINT UNSIGNED SQL type
123
+ rq.AdaptedValue(1.4, rq.FloatType()) # -> FLOAT SQL type
124
+ rq.AdaptedValue("127.0.0.1", rq.InetType()) # -> INET SQL type (network address)
125
+ rq.AdaptedValue([4.3, 5.6], rq.VectorType()) # -> VECTOR SQL type (for AI embeddings)
126
+
127
+ # Also you can use `AdaptedValue.to_sql()` method to convert value into SQL
128
+ val = rq.AdaptedValue([2, 3, 4], rq.ArrayType(rq.IntegerType()))
129
+ val.to_sql("postgresql") # -> ARRAY [2,3,4]
130
+ ```
131
+
132
+ As we said, `AdaptedValue` also validates your value:
133
+ ```python
134
+ rq.AdaptedValue(4.5, rq.CharType()) # -> TypeError: expected str, got float
135
+ ```
136
+
137
+ > [!TIP]\
138
+ > **Important**: `AdaptedValue` is lazy. This means it keeps your value and never converts it to Rust and then SQL until needed.
139
+
140
+ #### Expr
141
+ Represents a SQL expression that can be built into SQL code.
142
+
143
+ This class provides a fluent interface for constructing complex SQL expressions
144
+ in a database-agnostic way. It supports arithmetic operations, comparisons,
145
+ logical operations, and database-specific functions.
146
+
147
+ The class automatically handles SQL injection protection and proper quoting
148
+ when building the final SQL statement.
149
+
150
+ Everything can be converted into `Expr`, such as built-in types, `datetime`, `uuid`, `AdaptedValue`, `Select`, etc.
151
+
152
+ **Basic**
153
+ ```python
154
+ import rapidquery as rp
155
+
156
+ rp.Expr(25) # -> 25 (literal value)
157
+ rp.Expr("Hello") # -> 'Hello' (literal value)
158
+ rp.Expr(rq.AdaptedValue('World')) # -> 'World' (literal value)
159
+
160
+ rp.Expr.col("id") # -> "id" (column reference)
161
+ rp.Expr.col("users.name") # -> "users"."name" (column reference)
162
+ rp.Expr(rq.ColumnRef("name", table="users")) # -> "users"."name" (column reference)
163
+ ```
164
+
165
+ **Comparisons**
166
+ ```python
167
+ rq.Expr.col("status") == "active" # -> "status" == 'active'
168
+ rq.Expr.col("age") > 16 # -> "age" > 16
169
+
170
+ # Note that `rq.all` is different from built-in `all`
171
+ rq.all(
172
+ rq.Expr.col("age") >= 18,
173
+ rq.Expr.col("subscription").is_null(), # same as rq.Expr.col("subscription").is_(Expr.null())
174
+ rq.Expr.col("status").in_(["pending", "approved", "active"])
175
+ ) # -> "age" >= 18 AND "subscription" IS NULL AND "status" IN ('pending', 'approved', 'active')
176
+
177
+ # Note that `rq.any` is different from built-in `any`
178
+ rq.any(
179
+ rq.Expr.col("is_admin").is_(True),
180
+ rq.Expr.col("is_moderator").is_not_null(), # same as rq.Expr.col("subscription").is_not(Expr.null())
181
+ rq.Expr.col("price").between(10.00, 50.00)
182
+ ) # -> "is_admin" IS TRUE OR "is_moderator" IS NOT NULL OR "price" BETWEEN 10.00 AND 50.00
183
+ ```
184
+
185
+ **Best Practices**
186
+ - Always use `Expr.col()` for column references: This ensures proper quoting for your target database
187
+ ```python
188
+ # Column reference (properly quoted identifier)
189
+ rq.Expr.col("user_name") # → "user_name"
190
+
191
+ # String literal (value)
192
+ rq.Expr("user_name") # → 'user_name'
193
+ ```
194
+
195
+ - Use `rapidquery.all()` and `rapidquery.any()` for logical combinations: More readable than chaining `&` and `|` operators
196
+ ```python
197
+ # Good
198
+ all(condition1, condition2, condition3)
199
+
200
+ # Less readable
201
+ condition1 & condition2 & condition3
202
+ ```
203
+
204
+ - Be careful with `Expr.custom()`: It bypasses all safety checks
205
+ ```python
206
+ # Dangerous - vulnerable to SQL injection
207
+ user_input = "'; DROP TABLE users; --"
208
+ Expr.custom(f"name = '{user_input}'")
209
+
210
+ # Safe
211
+ Expr.col("name") == user_input
212
+ ```
213
+
214
+ - Use database-specific features when necessary: But understand portability trade-offs
215
+ ```python
216
+ # PostgreSQL-specific but powerful
217
+ Expr.col("tags").pg_contains(["python"])
218
+
219
+ # More portable but may be less efficient
220
+ Expr.col("tags").like("%python%")
221
+ ```
222
+
223
+ #### Statement Builders
224
+ Statements are divided into 2 categories: `QueryStatement`, and `SchemaStatement`.
225
+
226
+ Some statements like `Select`, `Update`, `Delete`, `Insert`, ... are `QueryStatement`.
227
+ Other statements like `Table`, `AlterTable`, `Index`, ... are `SchemaStatement`.
228
+
229
+ `QueryStatement` class interface is:
230
+ ```python
231
+ class QueryStatement:
232
+ def build(self, backend: _Backends) -> typing.Tuple[str, typing.Tuple[AdaptedValue, ...]]:
233
+ """
234
+ Build the SQL statement with parameter values.
235
+ """
236
+ ...
237
+
238
+ def to_sql(self, backend: _Backends) -> str:
239
+ """
240
+ Build a SQL string representation.
241
+
242
+ **This method is unsafe and can cause SQL injection.** use `.build()` method instead.
243
+ """
244
+ ...
245
+ ```
246
+
247
+ `SchemaStatement` class interface is:
248
+ ```python
249
+ class SchemaStatement:
250
+ def to_sql(self, backend: _Backends) -> str:
251
+ """
252
+ Build a SQL string representation.
253
+ """
254
+ ...
255
+ ```
256
+
257
+ ### Query Statements
258
+ #### Query Select
259
+ `Select` provides a chainable API for constructing SELECT queries with support for:
260
+ - Column selection with expressions and aliases
261
+ - Table and subquery sources
262
+ - Filtering with WHERE and HAVING
263
+ - Joins (inner, left, right, full, cross, lateral)
264
+ - Grouping and aggregation
265
+ - Ordering and pagination
266
+ - Set operations (UNION, EXCEPT, INTERSECT)
267
+ - Row locking for transactions
268
+ - DISTINCT queries
269
+
270
+ **Simple**
271
+ ```python
272
+ query = (
273
+ rq.Select(rq.Expr.asterisk()) # Or rq.Select(rq.ASTERISK)
274
+ .from_table("users")
275
+ .where(rq.Expr.col("name").like(r"%linus%"))
276
+ )
277
+ sql, params = query.build("postgresql")
278
+ # -> SELECT * FROM "users" WHERE "name" LIKE $1
279
+
280
+ query = (
281
+ rq.Select(rq.Expr.col("product"), rq.Expr.col("price"), rq.Expr.col("category"))
282
+ .from_table("products")
283
+ .where(rq.Expr.col("price") > 50)
284
+ .order_by(rq.Expr.col("price"), "desc")
285
+ )
286
+ sql, params = query.build("postgresql")
287
+ # -> SELECT "product", "price", "category" FROM "products" WHERE "price" > $1 ORDER BY "price" DESC
288
+
289
+ query = (
290
+ rq.Select(
291
+ rq.SelectExpr(rq.FunctionCall.count(rq.ASTERISK), alias="total_customers"),
292
+ rq.SelectExpr(rq.FunctionCall.avg(rq.Expr.col("age")), alias="average_age"),
293
+ )
294
+ .from_table("customers")
295
+ )
296
+ sql, params = query.build("postgresql")
297
+ # -> SELECT COUNT(*) AS "total_customers", AVG("age") AS "average_age" FROM "customers"
298
+ ```
299
+
300
+ **Complex**
301
+ ```python
302
+ # This query would be easier to create by using `AliasedTable` class,
303
+ # which introduced in "Advanced" part of this page
304
+ query = (
305
+ rq.Select(
306
+ rq.Expr.col("c.customer_name"),
307
+ rq.SelectExpr(
308
+ rq.FunctionCall.count(rq.Expr.col("o.order_id")),
309
+ "total_orders"
310
+ ),
311
+ rq.SelectExpr(
312
+ rq.FunctionCall.sum(rq.Expr.col("oi.quantity") * rq.Expr.col("oi.unit_price")),
313
+ "total_spent"
314
+ ),
315
+ )
316
+ .from_table(rq.TableName("customers", alias="c"))
317
+ .join(
318
+ rq.TableName("orders", alias="o"),
319
+ rq.Expr.col("c.customer_id") == rq.Expr.col("o.customer_id"),
320
+ type="left"
321
+ )
322
+ .join(
323
+ rq.TableName("order_items", alias="oi"),
324
+ rq.Expr.col("o.order_id") == rq.Expr.col("oi.order_id"),
325
+ type="left"
326
+ )
327
+ .where(
328
+ rq.Expr.col("o.order_date") >= (datetime.datetime.now() - datetime.timedelta(days=360))
329
+ )
330
+ )
331
+ sql, params = query.build("postgresql")
332
+ # SELECT
333
+ # "c"."customer_name",
334
+ # COUNT("o"."order_id") AS "total_orders",
335
+ # SUM("oi"."quantity" * "oi"."unit_price") AS "total_spent"
336
+ # FROM "customers" AS "c"
337
+ # LEFT JOIN "orders" AS "o" ON "c"."customer_id" = "o"."customer_id"
338
+ # LEFT JOIN "order_items" AS "oi" ON "o"."order_id" = "oi"."order_id"
339
+ # WHERE "o"."order_date" >= $1
340
+ ```
341
+
342
+ #### Query Insert
343
+ `Insert` provides a chainable API for constructing INSERT queries with support for:
344
+ - Single or multiple row insertion
345
+ - Conflict resolution (UPSERT)
346
+ - RETURNING clauses
347
+ - REPLACE functionality
348
+ - Default values
349
+
350
+ ```python
351
+ query = (
352
+ rq.Insert()
353
+ .replace()
354
+ .into("glyph")
355
+ .values(aspect=5.15, image="12A")
356
+ )
357
+ sql, params = query.build("postgresql")
358
+ # REPLACE INTO "glyph" ("aspect", "image") VALUES ($1, $2)
359
+
360
+ query = (
361
+ rq.Insert()
362
+ .into("glyph")
363
+ .columns("aspect", "image")
364
+ .values(5.15, "12A")
365
+ .values(16, "14A")
366
+ .returning("id")
367
+ )
368
+ sql, params = query.build("postgresql")
369
+ # INSERT INTO "glyph" ("aspect", "image") VALUES ($1, $2), ($3, $4) RETURNING "id"
370
+
371
+ query = (
372
+ rq.Insert()
373
+ .into("users")
374
+ .values(username="awolverp", role="author")
375
+ .on_conflict(
376
+ rq.OnConflict("id")
377
+ .do_update("username")
378
+ )
379
+ )
380
+ sql, params = query.build("postgresql")
381
+ # INSERT INTO "users" ("username", "role") VALUES ($1, $2)
382
+ # ON CONFLICT ("id") DO UPDATE SET "username" = "excluded"."username"
383
+
384
+ query = (
385
+ rq.Insert()
386
+ .into("users")
387
+ .values(username="awolverp", role="author")
388
+ .on_conflict(
389
+ rq.OnConflict("id")
390
+ .do_update(role="member")
391
+ )
392
+ )
393
+ sql, params = query.build("postgresql")
394
+ # INSERT INTO "users" ("username", "role") VALUES ($1, $2)
395
+ # ON CONFLICT ("id") DO UPDATE SET "author" = $3
396
+ ```
397
+
398
+ #### Query Update
399
+ `Update` provides a chainable API for constructing UPDATE queries with support for:
400
+ - Setting column values
401
+ - WHERE conditions for filtering
402
+ - LIMIT for restricting update count
403
+ - ORDER BY for determining update order
404
+ - RETURNING clauses for getting updated data
405
+
406
+ ```python
407
+ query = (
408
+ rq.Update()
409
+ .table("glyph")
410
+ .values(aspect=5.15, image="12A")
411
+ .returning_all()
412
+ .order_by(rq.Expr.col("id"), "desc")
413
+ )
414
+ sql, params = query.build("postgresql")
415
+ # UPDATE "glyph" SET "aspect" = $1, "image" = $2 ORDER BY "id" DESC RETURNING *
416
+
417
+ query = (
418
+ rq.Update()
419
+ .table("wallets")
420
+ .values(amount=rq.Expr.col("amount") + 10)
421
+ .where(rq.Expr.col("id").between(10, 30))
422
+ )
423
+ sql, params = query.build("postgresql")
424
+ # UPDATE "wallets" SET "amount" = "amount" + $1 WHERE "id" BETWEEN $2 AND $3
425
+ ```
426
+
427
+ #### Query Delete
428
+ `Delete` provides a chainable API for constructing DELETE queries with support for:
429
+ - WHERE conditions for filtering
430
+ - LIMIT for restricting deletion count
431
+ - ORDER BY for determining deletion order
432
+ - RETURNING clauses for getting deleted data
433
+
434
+ ```python
435
+ query = (
436
+ rq.Delete()
437
+ .from_table("users")
438
+ .where(
439
+ rq.all(
440
+ rq.Expr.col("id") > 10,
441
+ rq.Expr.col("id") < 30,
442
+ )
443
+ )
444
+ .limit(10)
445
+ )
446
+ sql, params = query.build("postgresql")
447
+ # DELETE FROM "users" WHERE "id" > $1 AND "id" < $2 LIMIT $3
448
+ ```
449
+
450
+ ### More About Queries
451
+ #### Custom Functions
452
+ For working with functions in RapidQuery, you have to use `FunctionCall` class.
453
+ A lot of functions such as `SUM`, `AVG`, `MD5`, ... is ready to use. For example:
454
+
455
+ ```python
456
+ expr = rq.FunctionCall.sum(rq.Expr.col("amount"))
457
+ expr.to_sql("postgresql") # -> SUM("amount")
458
+ ```
459
+
460
+ But for functions not provided by the library, you can define custom functions.
461
+ Custom functions can be defined using the `FunctionCall` constructor:
462
+
463
+ ```python
464
+ unknown = rq.FunctionCall("UNKNOWN").arg(rq.ASTERISK)
465
+ expr.to_sql("postgresql") # -> UNKNOWN(*)
466
+ ```
467
+
468
+ ### Schema Statements
469
+ #### Table Create
470
+ `Table` represents a complete database table definition.
471
+
472
+ This class encapsulates all aspects of a table structure including:
473
+ - Column definitions with their types and constraints
474
+ - Indexes for query optimization
475
+ - Foreign key relationships for referential integrity
476
+ - Check constraints for data validation
477
+ - Table-level options like engine, collation, and character set
478
+
479
+ Used to generate CREATE TABLE SQL statements with full schema specifications.
480
+
481
+ ```python
482
+ table = rq.Table(
483
+ "users",
484
+ [
485
+ rq.Column("id", rq.BigIntegerType(), primary_key=True, auto_increment=True),
486
+ rq.Column("name", rq.StringType(64), nullable=False),
487
+ rq.Column("username", rq.StringType(64), nullable=True, default=None),
488
+ rq.Column("subscription_id", rq.BigIntegerType(), nullable=False),
489
+ rq.Column("created_at", rq.DateTimeType(), default=rq.FunctionCall.now()),
490
+ ],
491
+ indexes=[
492
+ rq.Index(["created_at"], if_not_exists=True),
493
+ ],
494
+ foreign_keys=[
495
+ rq.ForeignKey(
496
+ from_columns=["subscription_id"],
497
+ to_columns=["id"],
498
+ to_table="subscriptions",
499
+ ),
500
+ ],
501
+ if_not_exists=True,
502
+ )
503
+ table.to_sql("postgresql")
504
+ # CREATE TABLE IF NOT EXISTS "users" (
505
+ # "id" bigserial PRIMARY KEY,
506
+ # "name" varchar(64) NOT NULL,
507
+ # "username" varchar(64) NULL DEFAULT NULL,
508
+ # "subscription_id" bigint NOT NULL,
509
+ # "created_at" datetime DEFAULT NOW(),
510
+ # CONSTRAINT "fk__subscription_id_subscriptions_id" FOREIGN KEY ("subscription_id") REFERENCES "subscriptions" ("id")
511
+ # );
512
+ # CREATE INDEX IF NOT EXISTS "ix_users_created_at" ON "users" ("created_at");
513
+ ```
514
+
515
+ > [!TIP]\
516
+ > We will use `Table` in [**ORM-like**](#orm-like) part of this page to create query statements.
517
+
518
+ #### Table Alter
519
+ `AlterTable` represents an ALTER TABLE SQL statement.
520
+
521
+ Provides a flexible way to modify existing table structures by applying
522
+ one or more alteration operations such as adding/dropping columns,
523
+ modifying column definitions, or managing constraints.
524
+
525
+ Multiple operations can be batched together in a single ALTER TABLE
526
+ statement for efficiency.
527
+
528
+ ```python
529
+ stmt = rq.AlterTable(
530
+ "users",
531
+ [
532
+ rq.AlterTableAddColumnOption(
533
+ rq.Column("updated_at", rq.TimestampWithTimeZoneType(), default=rq.FunctionCall.now())
534
+ ),
535
+ rq.AlterTableAddForeignKeyOption(
536
+ rq.ForeignKey(
537
+ from_columns=["wallet_id"],
538
+ to_columns=["id"],
539
+ to_table="wallets",
540
+ on_delete="CASCADE",
541
+ )
542
+ ),
543
+ rq.AlterTableDropColumnOption("deprecated"),
544
+ rq.AlterTableDropForeignKeyOption("fk__contraint_name"),
545
+ rq.AlterTableModifyColumnOption(rq.Column("created_at", rq.TimestampType())),
546
+ rq.AlterTableRenameColumnOption("oldname", "newname"),
547
+ ],
548
+ )
549
+ stmt.to_sql("postgresql")
550
+ # ALTER TABLE "users" ADD COLUMN "updated_at" timestamp with time zone DEFAULT NOW(),
551
+ # ADD CONSTRAINT "fk__wallet_id_wallets_id" FOREIGN KEY ("wallet_id") REFERENCES "wallets" ("id") ON DELETE CASCADE,
552
+ # DROP COLUMN "deprecated", DROP CONSTRAINT "fk__contraint_name",
553
+ # ALTER COLUMN "created_at" TYPE timestamp,
554
+ # RENAME COLUMN "oldname" TO "newname"
555
+ ```
556
+
557
+ #### Table Drop
558
+ `DropTable` represents a DROP TABLE SQL statement.
559
+
560
+ Builds table deletion statements with support for:
561
+ - Conditional deletion (IF EXISTS) to avoid errors
562
+ - CASCADE to drop dependent objects
563
+ - RESTRICT to prevent deletion if dependencies exist
564
+
565
+ ```python
566
+ stmt = rq.DropTable("users", if_exists=True)
567
+ stmt.to_sql("postgresql")
568
+ # DROP TABLE IF EXISTS "users"
569
+ ```
570
+
571
+ #### Table Rename
572
+ `RenameTable` represents a RENAME TABLE SQL statement.
573
+
574
+ Changes the name of an existing table to a new name. Both names can be
575
+ schema-qualified if needed.
576
+
577
+ ```python
578
+ stmt = rq.RenameTable("public.old_users", "archive.users")
579
+ stmt.to_sql("postgresql")
580
+ # ALTER TABLE "public"."old_users" RENAME TO "archive"."users"
581
+ ```
582
+
583
+ #### Table Truncate
584
+ `TruncateTable` Represents a TRUNCATE TABLE SQL statement.
585
+
586
+ Quickly removes all rows from a table, typically faster than DELETE
587
+ and with different transaction and trigger behavior depending on the
588
+ database system.
589
+
590
+ ```python
591
+ stmt = rq.TruncateTable("temp_data")
592
+ stmt.to_sql("postgresql")
593
+ # TRUNCATE TABLE "temp_data"
594
+ ```
595
+
596
+ #### Index Create
597
+ `Index` represents a database index specification.
598
+
599
+ This class defines the structure and properties of a database index,
600
+ including column definitions, uniqueness constraints, index type,
601
+ and partial indexing conditions.
602
+
603
+ ```python
604
+ stmt = rq.Index(
605
+ ["user_id", "reseller_id"],
606
+ "ix_users_user_reseller_id",
607
+ table="users",
608
+ if_not_exists=True,
609
+ )
610
+ stmt.to_sql("postgresql")
611
+ # CREATE INDEX IF NOT EXISTS "ix_users_user_reseller_id" ON "users" ("user_id", "reseller_id")
612
+
613
+ stmt = rq.Index(
614
+ [rq.IndexColumn("name", prefix=8, order="desc")],
615
+ "ix_users_user_reseller_id",
616
+ table="users",
617
+ if_not_exists=True,
618
+ )
619
+ stmt.to_sql("postgresql")
620
+ # CREATE INDEX IF NOT EXISTS "ix_users_user_reseller_id" ON "users" ("name" (8) DESC)
621
+ ```
622
+
623
+ #### Index Drop
624
+ `DropIndex` represents a DROP INDEX SQL statement.
625
+
626
+ Builds index deletion statements with support for:
627
+ - Conditional deletion (IF EXISTS)
628
+ - Table-specific index dropping (for databases that require it)
629
+ - Proper error handling for non-existent indexes
630
+
631
+ ```python
632
+ stmt = rq.DropIndex("ix_users_user_reseller_id")
633
+ stmt.to_sql("postgresql")
634
+ # DROP INDEX "ix_users_user_reseller_id"
635
+ ```
636
+
637
+ ### Advanced Usage
638
+ #### ORM-like
639
+ `Table` class is not just for generating CREATE TABLE statements. It's designed to make developing
640
+ easier for you.
641
+
642
+ First you have to know some basics:
643
+ ```python
644
+ users = rq.Table(
645
+ "users",
646
+ [
647
+ rq.Column("id", rq.IntegerType()),
648
+ rq.Column("name", rq.CharType(255)),
649
+ ]
650
+ )
651
+
652
+ # You can access columns easily:
653
+ users.c.id # -> <Column "id" type=<IntegerType >>
654
+ users.c.name # -> <Column "name" type=<CharType length=255>>
655
+ users.c.not_exists # -> KeyError: 'not_exists'
656
+ ```
657
+
658
+ Now you can use this structure to create `Select`, `Update`, `Delete`, and `Insert` queries:
659
+ ```python
660
+ query = (
661
+ rq.Select(users.c.name)
662
+ .from_table(users)
663
+ .where(users.c.id.to_expr() == 2)
664
+ )
665
+ sql, params = query.build("postgresql")
666
+ # SELECT "users"."name" FROM "users" WHERE "users"."id" = $1
667
+ ```
668
+
669
+ #### Table Alias
670
+ Using `Table` for creating queries can help you to create queries easier, but again it's hard to
671
+ have aliases (e.g. `FROM users AS u`) in queries. So we have **`AliasedTable`** class to make it
672
+ easy.
673
+
674
+ Imagine this table:
675
+ ```python
676
+ employees = rq.Table(
677
+ "employees",
678
+ [
679
+ rq.Column("id", rq.IntegerType()),
680
+ rq.Column("first_name", rq.CharType(255)),
681
+ rq.Column("jon_title", rq.CharType(255)),
682
+ ]
683
+ )
684
+ ```
685
+
686
+ **Without AliasedTable**
687
+ ```python
688
+ query = (
689
+ rq.Select(
690
+ employees.c.id.to_column_ref().copy_with(table="emp"),
691
+ rq.SelectExpr(
692
+ employees.c.name.to_column_ref().copy_with(table="emp"),
693
+ "employee_name",
694
+ ),
695
+ employees.c.job_title.to_column_ref().copy_with(table="emp"),
696
+ rq.SelectExpr(employees.c.id.to_column_ref().copy_with(table="mgr"), "manager_id"),
697
+ rq.SelectExpr(
698
+ employees.c.name.to_column_ref().copy_with(table="mgr"),
699
+ "employee_name",
700
+ ),
701
+ rq.SelectExpr(
702
+ employees.c.job_title.to_column_ref().copy_with(table="mgr"), "manager_title"
703
+ ),
704
+ )
705
+ .from_table(employees.name.copy_with(alias="emp"))
706
+ .join(
707
+ employees.name.copy_with(alias="mgr"),
708
+ (
709
+ rq.Expr(employees.c.manager_id.to_column_ref().copy_with(table="emp"))
710
+ == employees.c.id.to_column_ref().copy_with(table="mgr")
711
+ ),
712
+ type="inner"
713
+ )
714
+ )
715
+ sql, params = query.build("postgresql")
716
+ # SELECT
717
+ # "emp"."id", "emp"."name" AS "employee_name",
718
+ # "emp"."job_title", "mgr"."id" AS "manager_id",
719
+ # "mgr"."name" AS "employee_name", "mgr"."job_title" AS "manager_title"
720
+ # FROM "employees" AS "emp"
721
+ # INNER JOIN "employees" AS "mgr" ON "emp"."manager_id" = "mgr"."id"
722
+ ```
723
+
724
+ It's so hard and unreadable.
725
+
726
+ **With AliasedTable**
727
+ ```python
728
+ emp = rq.AliasedTable(employees, "emp")
729
+ mgr = rq.AliasedTable(employees, "mgr")
730
+
731
+ query = (
732
+ rq.Select(
733
+ emp.c.id,
734
+ rq.SelectExpr(emp.c.name, "employee_name"),
735
+ emp.c.job_title,
736
+ rq.SelectExpr(emp.c.id, "manager_id"),
737
+ rq.SelectExpr(emp.c.name, "employee_name"),
738
+ rq.SelectExpr(emp.c.job_title, "manager_title"),
739
+ )
740
+ .from_table(emp)
741
+ .join(
742
+ mgr,
743
+ rq.Expr(emp.c.manager_id) == mgr.c.id,
744
+ type="inner",
745
+ )
746
+ )
747
+ sql, params = query.build("postgresql")
748
+ # SELECT
749
+ # "emp"."id", "emp"."name" AS "employee_name",
750
+ # "emp"."job_title", "mgr"."id" AS "manager_id",
751
+ # "mgr"."name" AS "employee_name", "mgr"."job_title" AS "manager_title"
752
+ # FROM "employees" AS "emp"
753
+ # INNER JOIN "employees" AS "mgr" ON "emp"."manager_id" = "mgr"."id"
754
+ ```
755
+
756
+ As you saw, it's much simpler.
757
+
758
+ ### Performance
759
+ #### Benchmarks
760
+
761
+ > [!NOTE]
762
+ > Benchmarks run on *Linux-6.15.11-2-MANJARO-x86_64-with-glibc2.42* with CPython 3.13. Your results may vary.
763
+
764
+ **Generating Insert Query 100,000x times**
765
+ ```python
766
+ # RapidQuery
767
+ query = rq.Select(rq.Expr.asterisk()).from_table("users").where(rq.Expr.col("name").like(r"%linus%")) \
768
+ .offset(20).limit(20)
769
+
770
+ query.to_sql('postgresql')
771
+
772
+ # PyPika
773
+ query = pypika.Query.from_("users").where(pypika.Field("name").like(r"%linus%")) \
774
+ .offset(20).limit(20).select("*")
775
+
776
+ str(query)
777
+ ```
778
+
779
+ ```
780
+ RapidQuery: 254ms
781
+ PyPika: 3983ms
782
+ ```
783
+
784
+ **Generating Select Query 100,000x times**
785
+ ```python
786
+ # RapidQuery
787
+ query = rq.Insert().into("glyph").columns("aspect", "image") \
788
+ .values(5.15, "12A") \
789
+ .values(16, "14A") \
790
+ .returning("id")
791
+
792
+ query.to_sql('postgresql')
793
+
794
+ # PyPika
795
+ query = pypika.Query.into("glyph").columns("aspect", "image") \
796
+ .insert(5.15, "12A") \
797
+ .insert(16, "14A")
798
+
799
+ str(query)
800
+ ```
801
+
802
+ ```
803
+ RapidQuery: 267ms
804
+ PyPika: 4299ms
805
+ ```
806
+
807
+ **Generating Update Query 100,000x times**
808
+ ```python
809
+ # RapidQuery
810
+ query = rq.Update().table("wallets").values(amount=rq.Expr.col("amount") + 10).where(rq.Expr.col("id").between(10, 30))
811
+
812
+ query.to_sql('postgresql')
813
+
814
+ # PyPika
815
+ query = pypika.Query.update("wallets").set("amount", pypika.Field("amount") + 10) \
816
+ .where(pypika.Field("id").between(10, 30))
817
+
818
+ str(query)
819
+ ```
820
+
821
+ ```
822
+ RapidQuery: 252ms
823
+ PyPika: 4412ms
824
+ ```
825
+
826
+ **Generating Delete Query 100,000x times**
827
+ ```python
828
+ # RapidQuery
829
+ query = rq.Delete().from_table("users") \
830
+ .where(
831
+ rq.all(
832
+ rq.Expr.col("id") > 10,
833
+ rq.Expr.col("id") < 30,
834
+ )
835
+ ) \
836
+ .limit(10)
837
+
838
+ query.to_sql('postgresql')
839
+
840
+ # PyPika
841
+ query = pypika.Query.from_("users") \
842
+ .where((pypika.Field("id") > 10) & (pypika.Field("id") < 30)) \
843
+ .limit(10).delete()
844
+
845
+ str(query)
846
+ ```
847
+
848
+ ```
849
+ RapidQuery: 240ms
850
+ PyPika: 4556ms
851
+ ```
852
+
853
+ #### Performance Tips
854
+ - Using [`ORM-like`](#orm-like) is always slower than using `Expr.col` and literal `str`
855
+ - "Less calls, more speed"; RapidQuery powered by Rust & SeaQuery, which made us very fast, and only thing that can effect speed, is object calls in Python.
856
+
857
+ ## Known Issues
858
+ ### Unmanaged Rust Panic Output in Error Handling
859
+ The library may encounter errors during SQL query construction, which are correctly raised as *RuntimeError* exceptions. For instance, this occurs when using a function that isn't supported by your target database. **While this error-raising behavior is intentional and logical, the issue is that unmanaged Rust panic information is also printed to stderr**. Currently, there is no way to suppress or manage this panic output. We are working to resolve this problem as much as possible in future updates.
860
+
861
+ ```python
862
+ expr = rq.Expr.col("id").pg_contained("text")
863
+ expr.to_sql("sqlite")
864
+
865
+ thread '<unnamed>' (19535) panicked at sea-query-0.32.7/src/backend/query_builder.rs:665:22:
866
+ not implemented
867
+ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
868
+ Traceback (most recent call last):
869
+ File "<python-input-1>", line 1, in <module>
870
+ expr.to_sql("sqlite")
871
+ ~~~~~^^^^^^^^^^
872
+ RuntimeError: build failed
873
+ ```
874
+
875
+ ## License
876
+ This repository is licensed under the [GNU GPLv3 License](LICENSE)
877
+