Skip to content

Commit 2688f9a

Browse files
committed
refactor(codeql_qdriver): migrate from query run to database run-queries
Replace per-db-per-query codeql query run with codeql database run-queries (all queries per db at once). Decode/csv output to <dbPath>/results/. Collect reads from db results dirs. resultRoot only for -collect output. Closes #6
1 parent 40f1dcc commit 2688f9a

23 files changed

Lines changed: 168 additions & 160 deletions

ci/malloc_test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ buildGrps:
2323

2424
# query
2525
queryconfig:
26-
resultRoot: codeqlResult # resultRoot stores all query results. Example: <resultRoot>/<path/to/query>/<repo>.csv
26+
resultRoot: codeqlResult # resultRoot only for -collect csv. Bqrs/csv per db: <dbPath>/results/lslightly/qlstat/<queryPathNoExt>.<fmt>
2727
parallelCore: 20 # parallel cores to run query
2828
queryRoot: qlsrc # query root. There should be codeql-pack.yaml in queryRoot directory
2929
queryGrps:

cmd/codeql_qdriver/main.go

Lines changed: 146 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,17 @@ package main
22

33
import (
44
"bufio"
5-
"context"
65
"flag"
76
"fmt"
87
"log"
98
"os"
109
"os/exec"
11-
"path"
1210
"path/filepath"
1311
"runtime"
1412
"slices"
1513
"strings"
1614
"sync"
1715
"sync/atomic"
18-
"time"
1916

2017
"github.com/Lslightly/qlstat/config"
2118
"github.com/Lslightly/qlstat/utils"
@@ -76,14 +73,90 @@ func main() {
7673
}
7774
}
7875

79-
type ErrorPair struct {
80-
runErr string
81-
decodeErr error
76+
// -- External args helper ------------------------------------------------
77+
78+
// buildGroupExternalArgs collects all --external=... options across all queries.
79+
// Since externals are defined at the QueryGroup level, all queries share them,
80+
// but we deduplicate to be safe.
81+
func buildGroupExternalArgs(queries []config.Query, extroot string) []string {
82+
seen := make(map[string]bool)
83+
var result []string
84+
for _, q := range queries {
85+
for _, ext := range q.ExternalOptions(extroot) {
86+
if !seen[ext] {
87+
seen[ext] = true
88+
result = append(result, ext)
89+
}
90+
}
91+
}
92+
return result
8293
}
8394

84-
type QueryStatus struct {
85-
repo string
86-
pair ErrorPair
95+
// -- Query execution -----------------------------------------------------
96+
97+
// runQueriesOnDB runs all queries on a single database using codeql database run-queries,
98+
// then decodes the resulting bqrs files to the target format.
99+
//
100+
// Command:
101+
//
102+
// codeql database run-queries --search-path=... --external=... -- <dbPath> <query1> <query2> ...
103+
//
104+
// Results are placed at <dbPath>/results/<queryPack>/<queryPathNoExt>.bqrs.
105+
// Decoded output goes to <dbPath>/results/<queryPack>/<queryPathNoExt>.<tgtFmt>.
106+
func runQueriesOnDB(cfg *config.Artifact, db config.DB, queries []config.Query, tgtFmt string) {
107+
// --- Phase 1: run-queries ---
108+
extArgs := buildGroupExternalArgs(queries, db.ExtDir())
109+
110+
queryPaths := make([]string, len(queries))
111+
for i, q := range queries {
112+
queryPaths[i] = q.QueryPath(cfg.QueryRoot)
113+
}
114+
115+
logDir := filepath.Join(cfg.PassLogDir("query"), db.Name)
116+
utils.MkdirAll(logDir)
117+
outFile, errFile := utils.CreateOutAndErr(filepath.Join(logDir, "run-queries"))
118+
defer outFile.Close()
119+
defer errFile.Close()
120+
121+
args := []string{
122+
"database", "run-queries",
123+
"--search-path=" + filepath.Join(utils.ProjectRoot(), "qlsrc/lib"),
124+
}
125+
args = append(args, extArgs...)
126+
args = append(args, "--")
127+
args = append(args, db.Path())
128+
args = append(args, queryPaths...)
129+
130+
cmd := exec.Command("codeql", args...)
131+
cmd.Stdout = outFile
132+
cmd.Stderr = errFile
133+
outFile.WriteString(cmd.String() + "\n")
134+
if err := cmd.Run(); err != nil {
135+
fmt.Fprintf(errFile, "codeql database run-queries failed: %v\n", err)
136+
}
137+
138+
// --- Phase 2: decode bqrs to target format ---
139+
decodeLogDir := filepath.Join(cfg.PassLogDir("decode"), db.Name)
140+
utils.MkdirAll(decodeLogDir)
141+
142+
for _, query := range queries {
143+
bqrsPath := filepath.Join(db.Path(), "results/lslightly/qlstat", query.PathNoExt()+".bqrs")
144+
csvPath := filepath.Join(db.Path(), "results/lslightly/qlstat", query.PathNoExt()+"."+tgtFmt)
145+
146+
decOut, decErr := utils.CreateOutAndErr(filepath.Join(decodeLogDir, query.Name()))
147+
cmd := exec.Command("codeql", "bqrs", "decode",
148+
"--format="+tgtFmt,
149+
bqrsPath,
150+
"--output="+csvPath)
151+
cmd.Stdout = decOut
152+
cmd.Stderr = decErr
153+
decOut.WriteString(cmd.String() + "\n")
154+
if err := cmd.Run(); err != nil {
155+
fmt.Fprintf(decErr, "codeql bqrs decode failed: %v\n", err)
156+
}
157+
decOut.Close()
158+
decErr.Close()
159+
}
87160
}
88161

89162
/*
@@ -93,67 +166,31 @@ dump stdout/stderr for ${db} in ${config.OutResultRoot}/${qScriptNameNoExt}/log/
93166
*/
94167
func queriesExec(cfg *config.Artifact, grp config.QueryGroup, tgtFmt string) {
95168
dbs := cfg.ConvStrSliceToDBSlice(grp.QueryDBs)
96-
bar := progressbar.Default(int64(len(dbs) * len(grp.Queries)))
97-
for _, qScript := range grp.Queries {
98-
query := config.CreateQuery(qScript, grp.Externals, grp.ExternalFiles)
99-
100-
queryLogDir := path.Join(cfg.PassLogDir("query"), query.PathNoExt())
101-
utils.MkdirAll(queryLogDir)
102-
utils.Remkdir(query.DirPath(cfg.ResultRoot))
103-
104-
var dbWg sync.WaitGroup
105-
for _, db := range dbs {
106-
dbWg.Add(1)
107-
go func(db config.DB, query config.Query) {
108-
defer dbWg.Done()
109-
queryForOneDB(cfg, db, query)
110-
bar.Describe(fmt.Sprintf("query: %-15s db: %-15s exts: %-15s", query.Name(), db.Name, query.ExternalsSingleString()))
111-
bar.Add(1)
112-
}(db, query)
113-
}
114-
/*
115-
currently multiple queries for one database is not supported
116-
`codeql database run-queries` supports multiple queries.
117-
But work to collect results from codeql-db/<db>/results is needed
118-
*/
119-
dbWg.Wait()
120-
decodeFilesInDir(cfg, tgtFmt, query)
169+
170+
// Create Query objects for all queries in the group
171+
queries := make([]config.Query, len(grp.Queries))
172+
for i, qScript := range grp.Queries {
173+
queries[i] = config.CreateQuery(qScript, grp.Externals, grp.ExternalFiles)
174+
}
175+
176+
bar := progressbar.Default(int64(len(dbs)))
177+
var wg sync.WaitGroup
178+
179+
for _, db := range dbs {
180+
wg.Add(1)
181+
go func(db config.DB) {
182+
defer wg.Done()
183+
runQueriesOnDB(cfg, db, queries, tgtFmt)
184+
bar.Describe(fmt.Sprintf("db: %-20s %d queries", db.Name, len(queries)))
185+
bar.Add(1)
186+
}(db)
121187
}
188+
189+
wg.Wait()
122190
bar.Close()
123191
}
124192

125-
/*
126-
codeql query run -d=${config.InDBRoot}/${repo} ${config.QueryRoot}/${qScript} --output=${qResultDir}/${repo} --search-path=./qlsrc/lib --external=$pred/${config.dbRoot}/${repo}/ext/$pred.csv
127-
*/
128-
func queryForOneDB(cfg *config.Artifact, db config.DB, query config.Query) {
129-
qResultDir := query.DirPath(cfg.ResultRoot)
130-
repoOut, repoErr := utils.CreateOutAndErr(filepath.Join(query.DirPath(cfg.PassLogDir("query")), db.Name))
131-
defer repoOut.Close()
132-
defer repoErr.Close()
133-
134-
ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Minute)
135-
defer cancel()
136-
137-
cmd := exec.CommandContext(ctx, "codeql",
138-
append([]string{
139-
"query",
140-
"run",
141-
fmt.Sprintf("-d=%s", db.Path()),
142-
fmt.Sprintf("--search-path=%s", filepath.Join(cfg.QueryRoot, "lib")),
143-
query.QueryPath(cfg.QueryRoot),
144-
fmt.Sprintf("--output=%s", filepath.Join(qResultDir, db.Name)+".bqrs"),
145-
},
146-
query.ExternalOptions(db.ExtDir())...,
147-
)...,
148-
)
149-
cmd.Stdout = repoOut
150-
cmd.Stderr = repoErr
151-
repoOut.WriteString(cmd.String() + "\n")
152-
// TODO report errors in query
153-
_ = cmd.Run()
154-
repoOut.Sync()
155-
repoErr.Sync()
156-
}
193+
// -- Decoding ------------------------------------------------------------
157194

158195
func checkDecodeTargetFmt(tgtFmt string) bool {
159196
if slices.Contains(validFmts, tgtFmt) {
@@ -164,116 +201,97 @@ func checkDecodeTargetFmt(tgtFmt string) bool {
164201
}
165202

166203
/*
167-
codeql bqrs decode --format=${tgtFmt} ${path}/${fileBase}.bqrs --output=${path}/${fileBase}.${tgtFmt}
204+
codeql bqrs decode --format=${tgtFmt} ${dbPath}/results/lslightly/qlstat/${queryPathNoExt}.bqrs --output=${dbPath}/results/lslightly/qlstat/${queryPathNoExt}.${tgtFmt}
168205
*/
169206
func decodeResults(cfg *config.Artifact, tgtFmt string) {
170207
checkDecodeTargetFmt(tgtFmt)
171208

172-
// decode bqrs only in query result dir
173209
for grpi, grp := range cfg.QueryGrps {
210+
dbs := cfg.ConvStrSliceToDBSlice(grp.QueryDBs)
174211
queries := grp.Queries
175-
bar := progressbar.Default(int64(len(queries)))
176-
defer bar.Close()
212+
213+
bar := progressbar.Default(int64(len(queries) * len(dbs)))
177214
for _, qScript := range queries {
178-
bar.Add(1)
179215
query := config.CreateQuery(qScript, grp.Externals, grp.ExternalFiles)
180-
bar.Describe(fmt.Sprintf("Grp %d: Decoding %s dir", grpi, query.Name()))
181-
decodeFilesInDir(cfg, tgtFmt, query)
182-
}
183-
}
184-
}
216+
for _, db := range dbs {
217+
bqrsPath := filepath.Join(db.Path(), "results/lslightly/qlstat", query.PathNoExt()+".bqrs")
218+
csvPath := filepath.Join(db.Path(), "results/lslightly/qlstat", query.PathNoExt()+"."+tgtFmt)
185219

186-
func decodeFilesInDir(cfg *config.Artifact, tgtFmt string, query config.Query) {
187-
decodeLogDir := filepath.Join(cfg.PassLogDir("decode"), query.PathNoExt())
188-
utils.MkdirAll(decodeLogDir)
220+
decodeLogDir := filepath.Join(cfg.PassLogDir("decode"), db.Name)
221+
utils.MkdirAll(decodeLogDir)
222+
decOut, decErr := utils.CreateOutAndErr(filepath.Join(decodeLogDir, query.Name()))
189223

190-
resultRoot := query.DirPath(cfg.ResultRoot)
191-
dirEntries, err := os.ReadDir(resultRoot)
192-
if err != nil {
193-
log.Fatal(err)
194-
}
195-
var wg sync.WaitGroup
196-
for _, de := range dirEntries {
197-
if de.IsDir() || filepath.Ext(de.Name()) != ".bqrs" {
198-
continue
224+
cmd := exec.Command("codeql", "bqrs", "decode",
225+
"--format="+tgtFmt,
226+
bqrsPath,
227+
"--output="+csvPath)
228+
cmd.Stdout = decOut
229+
cmd.Stderr = decErr
230+
decOut.WriteString(cmd.String() + "\n")
231+
_ = cmd.Run()
232+
decOut.Close()
233+
decErr.Close()
234+
235+
bar.Describe(fmt.Sprintf("Grp %d: decoding %s/%s", grpi, query.Name(), db.Name))
236+
bar.Add(1)
237+
}
199238
}
200-
wg.Add(1)
201-
go func() {
202-
defer wg.Done()
203-
outFile, errFile := utils.CreateOutAndErr(filepath.Join(decodeLogDir, de.Name()))
204-
defer outFile.Close()
205-
defer errFile.Close()
206-
tgtPath := filepath.Join(resultRoot, strings.TrimSuffix(de.Name(), filepath.Ext(de.Name()))+"."+tgtFmt)
207-
cmd := exec.Command("codeql", "bqrs", "decode",
208-
"--format="+tgtFmt,
209-
filepath.Join(resultRoot, de.Name()),
210-
"--output="+tgtPath)
211-
_ = cmd.Run()
212-
}()
239+
bar.Close()
213240
}
214-
wg.Wait()
215241
}
216242

243+
// -- CSV collection ------------------------------------------------------
244+
217245
func collectCSVs(cfg *config.Artifact) {
218246
for grpi, grp := range cfg.QueryGrps {
219247
bar := progressbar.Default(int64(len(grp.Queries)))
220248
for _, qScript := range grp.Queries {
221249
query := config.CreateQuery(qScript, grp.Externals, grp.ExternalFiles)
222250
bar.Describe(fmt.Sprintf("Grp %d: Collecting for "+query.PathNoExt(), grpi))
223-
collectCSVsForQuery(cfg, query)
251+
collectCSVsForQuery(cfg, query, grp)
224252
bar.Add(1)
225253
}
226254
}
227255
}
228256

229-
func collectCSVsForQuery(cfg *config.Artifact, query config.Query) {
230-
qResultDir := query.DirPath(cfg.ResultRoot)
231-
qResultCSV := qResultDir + ".csv"
257+
func collectCSVsForQuery(cfg *config.Artifact, query config.Query, grp config.QueryGroup) {
258+
qResultCSV := query.DirPath(cfg.ResultRoot) + ".csv"
232259

233-
// Create or truncate the output CSV file
234260
outFile := utils.CreateFile(qResultCSV)
235261
defer outFile.Close()
236262

237-
// Read all CSV files in the result directory
238-
files, err := os.ReadDir(qResultDir)
239-
if err != nil {
240-
log.Fatalf("Error reading directory %s: %v", qResultDir, err)
241-
}
242-
263+
dbs := cfg.ConvStrSliceToDBSlice(grp.QueryDBs)
243264
var ticket atomic.Int64
244265
var headerWritten atomic.Bool
245266
var expectedHeader string
246267
contentChan := make(chan string, cfg.ParallelCore)
247268
var hasFile bool
248269

249270
var wg sync.WaitGroup
250-
for _, file := range files {
251-
if file.IsDir() || filepath.Ext(file.Name()) != ".csv" {
271+
for _, db := range dbs {
272+
csvPath := filepath.Join(db.Path(), "results/lslightly/qlstat", query.PathNoExt()+".csv")
273+
274+
// Check if the csv file exists
275+
if _, err := os.Stat(csvPath); os.IsNotExist(err) {
252276
continue
253277
}
254278
hasFile = true
255279

256280
wg.Add(1)
257-
go func(file os.DirEntry, qResultDir string) {
281+
go func(csvPath, dbName string) {
258282
defer wg.Done()
259-
// Get repo name from filename (without .csv extension)
260-
repoName := strings.TrimSuffix(file.Name(), ".csv")
261283

262-
// For the first file, write header with repo_name column
263-
fpath := filepath.Join(qResultDir, file.Name())
264-
// Open the CSV file
265-
inFile, err := os.Open(fpath)
284+
inFile, err := os.Open(csvPath)
266285
if err != nil {
267-
log.Fatalf("Error opening file %s: %v", fpath, err)
286+
log.Fatalf("Error opening file %s: %v", csvPath, err)
268287
}
269288
defer inFile.Close()
270289

271290
scanner := bufio.NewScanner(inFile)
272291

273292
if !scanner.Scan() {
274-
log.Fatalf("Error reading header from %s", fpath)
293+
log.Fatalf("Error reading header from %s", csvPath)
275294
}
276-
// Read header
277295
currentHeader := scanner.Text()
278296

279297
firstFile := ticket.Add(1) == 1
@@ -284,7 +302,7 @@ func collectCSVsForQuery(cfg *config.Artifact, query config.Query) {
284302
for !headerWritten.Load() {
285303
}
286304
if currentHeader != expectedHeader {
287-
log.Fatalf("Warning: Header mismatch in file %s. Expected: %s, Got: %s", fpath, expectedHeader, currentHeader)
305+
log.Fatalf("Warning: Header mismatch in file %s. Expected: %s, Got: %s", csvPath, expectedHeader, currentHeader)
288306
}
289307
}
290308
lines := make([]string, 0)
@@ -293,10 +311,10 @@ func collectCSVsForQuery(cfg *config.Artifact, query config.Query) {
293311
if line == "" {
294312
break
295313
}
296-
lines = append(lines, line+","+repoName)
314+
lines = append(lines, line+","+dbName)
297315
}
298316
contentChan <- strings.Join(lines, "\n")
299-
}(file, qResultDir)
317+
}(csvPath, db.Name)
300318
}
301319
go func() {
302320
wg.Wait()

0 commit comments

Comments
 (0)