rapidquery 0.1.0a1__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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
|
+
|