Skip to content

Commit f524f15

Browse files
committed
feat: add support for using query parameters
Fix #23
1 parent b9c08ce commit f524f15

File tree

8 files changed

+38
-12
lines changed

8 files changed

+38
-12
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Generates the endpoints (or a whole app) from a mapping (SQL query -> URL)
1919
query: >-
2020
SELECT id, name, name_ru, slug
2121
FROM categories
22+
LIMIT :q.limit
2223
post:
2324
query: >-
2425
INSERT INTO categories(name, slug, created_at, created_by, updated_at, updated_by)
@@ -41,7 +42,7 @@ Generates the endpoints (or a whole app) from a mapping (SQL query -> URL)
4142
FROM categories
4243
WHERE id = :p.categoryId
4344
```
44-
Note that the queries use a little unusual named parameters: `:b.name`, `:p.categoryId`, etc The prefixes `b` (body) and `p` (path) are used here in order to bind to parameters from the appropriate sources. The prefixes are needed only during code generation and they will absent from the resulted code.
45+
Note that the queries use a little unusual named parameters: `:b.name`, `:p.categoryId`, etc The prefixes `q` (query), `b` (body) and `p` (path) are used here in order to bind to parameters from the appropriate sources. The prefixes are needed only during code generation and they will absent from the resulted code.
4546

4647
1. Generate code
4748
| Language | Command for code generation | Example of generated files | Libraries |

examples/go/routes.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,18 @@ func registerRoutes(r chi.Router, db *sqlx.DB) {
7676
})
7777

7878
r.Get("/v1/categories", func(w http.ResponseWriter, r *http.Request) {
79+
nstmt, err := db.PrepareNamed("SELECT id , name , name_ru , slug FROM categories LIMIT :limit")
80+
if err != nil {
81+
fmt.Fprintf(os.Stderr, "PrepareNamed failed: %v\n", err)
82+
w.WriteHeader(http.StatusInternalServerError)
83+
return
84+
}
85+
7986
var result []CategoryDto
80-
err := db.Select(&result, "SELECT id , name , name_ru , slug FROM categories")
87+
args := map[string]interface{}{
88+
"limit": r.URL.Query().Get("limit"),
89+
}
90+
err = nstmt.Get(&result, args)
8191
switch err {
8292
case sql.ErrNoRows:
8393
w.WriteHeader(http.StatusNotFound)

examples/js/endpoints.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
, name
3636
, name_ru
3737
, slug
38-
FROM categories
38+
FROM categories
39+
LIMIT :q.limit
3940
dto:
4041
name: CategoryDto
4142
fields:

examples/js/routes.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ app.get('/v1/collections/:collectionId/categories/count', (req, res) => {
3636

3737
app.get('/v1/categories', (req, res) => {
3838
pool.query(
39-
'SELECT id , name , name_ru , slug FROM categories',
39+
'SELECT id , name , name_ru , slug FROM categories LIMIT :limit',
40+
{ "limit": req.query.limit },
4041
(err, rows, fields) => {
4142
if (err) {
4243
throw err

examples/python/routes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ def get_v1_collections_collection_id_categories_count(collectionId, conn = Depen
6060
conn.close()
6161

6262
@router.get('/v1/categories')
63-
def get_list_v1_categories(conn = Depends(db_connection)):
63+
def get_list_v1_categories(limit, conn = Depends(db_connection)):
6464
try:
6565
with conn:
6666
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
67-
cur.execute("SELECT id , name , name_ru , slug FROM categories")
67+
cur.execute("SELECT id , name , name_ru , slug FROM categories LIMIT %(limit)s", { "limit": limit })
6868
return cur.fetchall()
6969
finally:
7070
conn.close()

src/cli.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ const createApp = async (destDir, lang) => {
9292
// "SELECT *\n FROM foo" => "SELECT * FROM foo"
9393
const flattenQuery = (query) => query.replace(/\n[ ]*/g, ' ');
9494

95-
// "WHERE id = :p.categoryId OR id = :b.id" => "WHERE id = :categoryId OR id = :id"
96-
const removePlaceholders = (query) => query.replace(/(?<=:)[pb]\./g, '');
95+
// "WHERE id = :p.categoryId OR id = :b.id LIMIT :q.limit" => "WHERE id = :categoryId OR id = :id LIMIT :limit"
96+
const removePlaceholders = (query) => query.replace(/(?<=:)[pbq]\./g, '');
9797

9898
// "/categories/:id" => "/categories/{id}"
9999
// (used only with Golang's go-chi)
@@ -152,6 +152,7 @@ const createEndpoints = async (destDir, lang, config) => {
152152
'js': {
153153
'p': 'req.params',
154154
'b': 'req.body',
155+
'q': 'req.query',
155156
},
156157
'go': {
157158
'p': function(param) {
@@ -160,6 +161,9 @@ const createEndpoints = async (destDir, lang, config) => {
160161
'b': function(param) {
161162
return 'dto.' + capitalize(snake2camelCase(param));
162163
},
164+
'q': function(param) {
165+
return `r.URL.Query().Get("${param}")`
166+
},
163167
}
164168
}
165169

@@ -171,7 +175,11 @@ const createEndpoints = async (destDir, lang, config) => {
171175
"endpoints": config,
172176

173177
// "... WHERE id = :p.id" => [ "p.id" ]
174-
"extractParamsFromQuery": (query) => query.match(/(?<=:)[pb]\.\w+/g) || [],
178+
"extractParamsFromQuery": (query) => query.match(/(?<=:)[pbq]\.\w+/g) || [],
179+
180+
// "p.id" => "id" + the same for "q" and "b"
181+
// (used only with FastAPI)
182+
"stipOurPrefixes": (str) => str.replace(/^[pbq]\./, ''),
175183

176184
// "/categories/:categoryId" => [ "categoryId" ]
177185
// (used only with FastAPI)
@@ -230,6 +238,8 @@ const createEndpoints = async (destDir, lang, config) => {
230238
}
231239
).join('\n\t\t\t');
232240
},
241+
242+
"placeholdersMap": placeholdersMap,
233243
}
234244
);
235245

src/templates/routes.go.ejs

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ endpoints.forEach(function(endpoint) {
193193
194194
var result <%- dataType %>
195195
args := map[string]interface{}{
196-
<%- params.map(p => `"${p.substring(2)}": chi.URLParam(r, "${p.substring(2)}"),`).join('\n\t\t\t') %>
196+
<%- /* LATER: extract */ params.map(p => `"${p.substring(2)}": ${placeholdersMap['go'][p.substring(0, 1)](p.substring(2))},`).join('\n\t\t\t') %>
197197
}
198198
err = nstmt.Get(&result, args)
199199
<% } else { -%>

src/templates/routes.py.ejs

+5-2
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@ function convertToFastApiPath(path) {
3535
3636
endpoints.forEach(function(endpoint) {
3737
const path = convertToFastApiPath(endpoint.path)
38-
const methodArgs = extractParamsFromPath(endpoint.path)
39-
methodArgs.push('conn = Depends(db_connection)')
38+
const argsFromPath = extractParamsFromPath(endpoint.path)
4039
4140
endpoint.methods.forEach(function(method) {
4241
const hasGetOne = method.name === 'get'
4342
const hasGetMany = method.name === 'get_list'
4443
const pythonMethodName = generate_method_name(method.name, path)
4544
45+
// LATER: add support for aggregated_queries (#17)
46+
const argsFromQuery = method.query ? extractParamsFromQuery(method.query).map(stipOurPrefixes) : []
47+
const methodArgs = Array.from(new Set([...argsFromPath, ...argsFromQuery, 'conn = Depends(db_connection)']))
48+
4649
const queriesWithNames = []
4750
if (method.query) {
4851
queriesWithNames.push({ "result" : method.query })

0 commit comments

Comments
 (0)