Skip to content

jasonKercher/fql

Repository files navigation

  __                _
 / _|     __ _     | |
| |_     / _` |    | |
|  _|ile| (_| |uery| |anguage
|_|      \__, |    |_|
            |_|

This is an ANTLR powered text processing language for Linux. Similar in spirit to awk which uses a C-like syntax, fql uses SQL (T-SQL specifically).

What can it do?

fql gives you the power of SQL for your delimited or fixed-width data files without the need to import them into a database. fql aims to be fast as well as memory efficient. Most queries should use a relatively low amount of memory. Keep in mind that memory consumption increases for things like GROUP BY, ORDER BY and JOIN. Queries are case insensitive so SELECT * FROM T1 WHERE FOO = 'BAR' is the same as select * from t1 where foo = 'bar'.

fql.mp4

What can it not do?

  • windowed-functions (like RANK)
  • RIGHT and FULL JOIN. INNER and LEFT JOIN work.
  • TOP PERCENT
  • WITH / common table expressions
  • COUNT([column]) -- All counts are treated as COUNT(**)
  • MERGE
  • APPLY
  • UNION without ALL
  • PIVOT/UNPIVOT
  • Implicit UPDATE/DELETE into subqueries
  • UPDATE/DELETE TOP (...)

Most recent and possibly unstable feature(s):

  • fql --thread (-t)

Is it fast?

Here is a naive benchmark vs other similar projects:

  • textql
  • q
  • csvsql
  • sqlite: Just for comparison, let's import the tables into sqlite ahead of time.
  • base benchmark with shell tools

Benchmarks are performed on 2 tables of generic random data of 2 000 000 records (not including header). The gentsv.sh script can be used to build these files.

:) ./gentsv.sh 2000000 > t2.temp
:) wc -l t2.temp
2000001 t2.temp
:) head -10 t2.temp
foo     bar     baz
f493263f        5b      7791
c79cfff4        e0      19075
3157f48c        8e      83146
7f34ca2e        82      19950
a464be18        97      95934
bb193135        6c      38038
c6c04cf4        57      185937
a36ab33b        5b      45490
a05c7214        b1      143027

RESULTS

  1. JOIN t1 to t2 on foo
program time
fql -t 2.095s
fql 2.620s
shell tools 6.435s
sqlite3 20.560s
q 53.742s
csvsql 2m50.405s
textql N/A #
csvq N/A #
  1. COUNT(*) GROUP BY bar
program time
shell tools 0.630s
fql -t 0.842s
fql 0.980s
sqlite3 1.160s
csvq 4.213s
q 15.405s
textql 17.690s
csvsql 1m57.014s
  1. SELECT with LIKE '%aa[0-9]aa%'
program time
sqlite3 0.211s *
shell tools 0.470s
fql -t 0.738s
fql 0.745s
csvq 4.089s
q 13.737s *
textql 14.154s
csvsql 1m56.471s *
  1. ORDER BY
program time
sqlite3 2.105s
shell tools 3.125s *
fql -t 5.654s
fql 5.779s
csvq 11.926s
textql 22.980s
q 42.524s
csvsql 2m6.498s

* See benchmark.sh for additional notes

Installation

Arch Linux If you are using an Arch Linux based distribution, fql can be retrieved from the AUR.

Compiling from source

Requirements

  • libcsv: for reading and writing correct csv files based on RFC 4180
  • antlr4 C++ runtime library: This is available though pacman (pacman -S antlr4-runtime) if you have an Arch Linux based distribution. I struggled getting this installed on Ubuntu and just wound up compiling it.
  • libpcre2: for LIKE statement implementation
  • libcheck: This is only for make check.

Once antlr4 runtime is installed, we need to tell the configure script where to find the antlr header files. These headers are installed at ${PREFIX}/include/antlr4-runtime where $PREFIX is the install prefix for the antlr4-runtime. So we need to set ANTLR4_CPATH (ANTLR4_CPATH=${PREFIX}/include) Rather than searching for it manually, you can use the find_antlr_cpath.sh script to find it for you:

./configure
ANTLR4_CPATH=$(./find_antlr_cpath.sh) make
# Or if you know what it is...
ANTLR4_CPATH=/usr/lib make
make check   # optional
make install

Installed program: fql

Installed header: fql.h

Installed library: libfql.so

Library

The fql program was designed to work within a shell environment, however, it can also be utilized as a library. In fact, the fql program is really just a thin wrapper for fql_exec library function. Here is a quick example of how to use the library API:

#include <stdlib.h>
#include "fql.h"

int main(int argc, char** argv)
{
	struct fql_handle* handle = fql_new();
	int ret = fql_make_plans(handle,
	                         "select t2.*           "
	                         "from t1               "
	                         "left join t2          "
	                         "    on t1.foo = t2.foo");

	if (ret == FQL_FAIL) {
		fql_free(handle);
		return EXIT_FAILURE;
	}

	// In case we don't know how many fields will return...
	int field_count = fql_field_count(handle);

	// fql_step will initialize this for us
	struct fql_field* fields = NULL;

	// fql_step returns...
	//     FQL_FAIL: runtime error
	//     0:        complete
	//     1:        still running
	while ((ret = fql_step(handle, &fields)) == 1) {

		// Based on the query (it's a LEFT JOIN), there
		// is a chance this field could return NULL. We
		// can check for this with the is_null flag...
		if (fields[0].is_null) {
			// do something about it...
			continue;
		}

		// Since we are using the default schema,
		// we can assume this is of type FQL_STRING.
		// If we had to check though...
		if (fields[0].type == FQL_STRING) {
			const char* foo = fields[0].data.s;
			// foo now contains the first value of
			// the first field returned by the query
		}
	}

	fql_free(handle);
	return ret;
}