Skip to content

Commit 592a50e

Browse files
authored
Merge pull request #12 from VictoriaMetrics/issue-11-rows-limit
Implemented server-side configuration for default max rows limit (#11)
2 parents c006596 + 3fc1733 commit 592a50e

File tree

6 files changed

+43
-14
lines changed

6 files changed

+43
-14
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ Example (`config.json`):
112112
"errors": "* | level:ERROR",
113113
"traces": "* | span_id:*"
114114
},
115-
"viewsDir": "./data/views"
115+
"viewsDir": "./data/views",
116+
"limit": 1000
116117
}
117118
```
118119

@@ -123,6 +124,7 @@ Example (`config.json`):
123124
| `bearerToken` | string | Optional bearer token injected into VictoriaLogs requests when `endpoint` is set. | empty |
124125
| `tables` | map[string]string | Mapping from SQL table name to LogsQL filter or pipeline fragment. Keys are case-insensitive. | `{ "logs": "*" }` |
125126
| `viewsDir` | string | Directory that stores `.logsql` files for views. Required for `CREATE VIEW`, `DROP VIEW`, and `SHOW VIEWS`. | `./data/views` |
127+
| `limit` | int | Maximum number of rows returned by any query. | 1000 |
126128

127129
Please note that VictoriaLogs is called via the backend, so if you are using sql-to-logsql in Docker, localhost refers to the localhost of the container, not your computer.
128130

@@ -195,12 +197,15 @@ Successful response:
195197
Errors emit `HTTP 4xx/5xx` with `{ "error": "..." }`.
196198
Parser, translator, VictoriaLogs client, and view-store errors map to informative messages (`400`, `409`, `423`, `502`, etc.).
197199

198-
### `GET /api/v1/endpoint`
200+
### `GET /api/v1/config`
199201

200-
Returns the compile-time endpoint configured on the server (used by the UI to decide whether the endpoint fields should be read-only):
202+
Returns the endpoint and max rows limit configured on the server (used by the UI to decide whether the endpoint fields should be read-only):
201203

202204
```json
203-
{ "endpoint": "https://victoria-logs.example.com" }
205+
{
206+
"endpoint": "https://victoria-logs.example.com",
207+
"limit": 1000
208+
}
204209
```
205210

206211
### `GET /healthz`

cmd/sql-to-logsql/api/server.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Config struct {
3030
BearerToken string `json:"bearerToken"`
3131
Tables map[string]string `json:"tables"`
3232
ViewsDir string `json:"viewsDir"`
33+
Limit uint32 `json:"limit"`
3334
}
3435

3536
type Server struct {
@@ -62,20 +63,23 @@ func NewServer(cfg Config) (*Server, error) {
6263
srv := &Server{
6364
mux: http.NewServeMux(),
6465
sp: sp,
65-
api: vlogs.NewVLogsAPI(vlogs.EndpointConfig{
66-
Endpoint: serverCfg.Endpoint,
67-
BearerToken: serverCfg.BearerToken,
68-
}),
66+
api: vlogs.NewVLogsAPI(
67+
vlogs.EndpointConfig{
68+
Endpoint: serverCfg.Endpoint,
69+
BearerToken: serverCfg.BearerToken,
70+
},
71+
serverCfg.Limit,
72+
),
6973
}
7074
srv.mux.HandleFunc("/healthz", withSecurityHeaders(srv.handleHealth))
7175
srv.mux.HandleFunc("/api/v1/sql-to-logsql", withSecurityHeaders(srv.handleQuery))
72-
srv.mux.HandleFunc("/api/v1/endpoint", withSecurityHeaders(func(w http.ResponseWriter, r *http.Request) {
76+
srv.mux.HandleFunc("/api/v1/config", withSecurityHeaders(func(w http.ResponseWriter, r *http.Request) {
7377
if r.Method != http.MethodGet {
7478
w.Header().Set("Allow", http.MethodGet)
7579
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
7680
return
7781
}
78-
writeJSON(w, http.StatusOK, map[string]string{"endpoint": serverCfg.Endpoint})
82+
writeJSON(w, http.StatusOK, map[string]any{"endpoint": serverCfg.Endpoint, "limit": serverCfg.Limit})
7983
}))
8084
srv.mux.HandleFunc("/", withSecurityHeaders(srv.handleStatic))
8185
return srv, nil

cmd/sql-to-logsql/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func main() {
3636
if len(cfg.Tables) == 0 {
3737
cfg.Tables = map[string]string{"logs": "*"}
3838
}
39+
if (cfg.Limit) <= 0 {
40+
cfg.Limit = 1000
41+
}
3942
srv, err := api.NewServer(cfg)
4043
if err != nil {
4144
log.Fatalf("failed to configure server: %v", err)

cmd/sql-to-logsql/web/ui/src/components/sql-editor/SQLEditor.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,24 @@ import {Select, SelectContent, SelectItem, SelectTrigger} from "@/components/ui/
1313
import {SelectValue} from "@radix-ui/react-select";
1414
import {DEFAULT_EXAMPLE_ID, EXAMPLES} from "@/components/sql-editor/examples.ts";
1515
import {COMPLETIONS} from "@/components/sql-editor/complections.ts";
16-
import {CircleXIcon, CircleCheckBigIcon, PlayIcon} from "lucide-react"
16+
import {CircleXIcon, CircleCheckBigIcon, PlayIcon, ListFilterIcon} from "lucide-react"
1717
import {Spinner} from "@/components/ui/spinner.tsx";
18+
import {Badge} from "@/components/ui/badge.tsx";
1819

1920
export interface SqlEditorProps {
2021
readonly onRun?: (sql: string) => void;
2122
readonly isLoading?: boolean;
2223
readonly error?: string;
2324
readonly success?: string;
25+
readonly limit?: number
2426
}
2527

2628
export function SQLEditor({
2729
onRun,
2830
isLoading,
2931
error,
3032
success,
33+
limit,
3134
}: SqlEditorProps) {
3235
const [value, setValue] = useState<string>(DEFAULT_EXAMPLE_ID);
3336
const [sql, setSql] = useState("");
@@ -141,6 +144,14 @@ export function SQLEditor({
141144
<span className={"text-green-700"}>{success}</span>
142145
</CardFooter>
143146
)}
147+
{!error && !success && limit && limit > 0 && (
148+
<CardFooter className={"flex gap-1"}>
149+
<ListFilterIcon />
150+
<span>
151+
Any query will be limited to <Badge variant={"secondary"} className={"font-semibold"}>{limit}</Badge> rows.
152+
</span>
153+
</CardFooter>
154+
)}
144155
</Card>
145156
);
146157
}

cmd/sql-to-logsql/web/ui/src/pages/main/Main.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@ export function Main() {
2727
const [loading, setLoading] = useState<boolean>(false);
2828
const [error, setError] = useState<string>("");
2929
const [success, setSuccess] = useState<string>("");
30+
const [limit, setLimit] = useState<number>(0);
3031

3132
useEffect(() => {
3233
setLoading(true);
33-
fetch(`/api/v1/endpoint`).then(resp => resp.json()).then(data => {
34+
fetch(`/api/v1/config`).then(resp => resp.json()).then(data => {
3435
if (data.endpoint) {
3536
setEndpointUrl(data.endpoint);
3637
setBearerToken("secret");
3738
setEndpointReadOnly(true);
3839
setEndpointEnabled(false);
3940
}
41+
setLimit(data.limit || 0);
4042
setLoading(false);
4143
})
4244
}, [])
@@ -103,6 +105,7 @@ export function Main() {
103105
isLoading={loading}
104106
error={error}
105107
success={success}
108+
limit={limit}
106109
/>
107110
</div>
108111
<Docs />

lib/vlogs/api.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ type EndpointConfig struct {
2020

2121
type API struct {
2222
ec EndpointConfig
23+
limit uint32
2324
client *http.Client
2425
}
2526

26-
func NewVLogsAPI(ec EndpointConfig) *API {
27+
func NewVLogsAPI(ec EndpointConfig, limit uint32) *API {
2728
return &API{
28-
ec: ec,
29+
ec: ec,
30+
limit: limit,
2931
client: &http.Client{
3032
Timeout: 60 * time.Second,
3133
},
@@ -89,6 +91,7 @@ func (a *API) Query(ctx context.Context, logsQL string, recEC EndpointConfig) ([
8991
reqURL = reqURL.JoinPath("/select/logsql/query")
9092
form := url.Values{}
9193
form.Set("query", logsQL)
94+
form.Set("limit", fmt.Sprintf("%d", a.limit))
9295
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL.String(), strings.NewReader(form.Encode()))
9396
if err != nil {
9497
return nil, &APIError{

0 commit comments

Comments
 (0)