Writing a SQLite clone from scratch in C
cstack.github.io
cstack.github.io
Query *q = db_select(db);
db_add_from(q, "persons");
db_left_join(q, "companies", db_expr(DB_EQ, "per_id", "com_id"));
Query *q = db_create_table(db);
db_add_column(q, "id", DB_TYPE_INT | DB_COL_NOT_NULL | DB_COL_PRIMARY);
db_set_table_name(q, "persons");
... and so on? That way we could combine and generate queries for specific engines without that ugly string joining/parsing.What you describe is useful from a programmer's point of view, but maybe the wrong abstraction. You could write such a thing which actually generates SQL from an API like you've defined. You could write an alternate ODBC engine.
But there is a lot of overhead for database authours to actually provide an API like you describe. It's far simpler to provide something SQL-compliant with an ODBC or JDBC driver and now you can plug in almost anywhere.
I have used a database engine that exposes primitives to build filters and similar programs to what you describe, and selectively push them to the server. It's not a very deep abstraction.
[1] http://docs.sqlalchemy.org/en/latest/orm/tutorial.html
[2] https://docs.djangoproject.com/en/1.11/topics/db/queries/
For now, even '?' placeholder escaping is actually manually-printf'ed under the driver's hood, afaik, so in the end we send plain text to the server. SQLite probably avoids that via sqlite3_bind() call, which allows to pass SQLITE3_STATIC string args if you want, but that's the benefit of in-process engine.
Placeholders are there for those who tries to concatenate queries by hand; having them properly escaped in protocol should not defeat client-side security purpose.
And then you come across \u0027 and you're screwed.
Edit: removed double backslash from literal to not confuse it with host language's escaping.
Edit: this call is most low-level db interface in lapis apps.
But even more, I think it's "in the nature of programming" that for a given system you have some parts are regularly structured and externally visible but other parts that seem like they ought to be visible and changable from outside but which aren't and can't easily be made so.
This might be because they use intermediate variable or it might be because they have changed the implementation over time or for a variety of reasons.
And this ultimately comes down to the difference between the way intuitively you'd think X feature should be implemented and real, much-more-messy way X feature is actually implemented - the difference internals and an API.
Having internals and externals be equally easy to work with was the dream of the object orientation but sadly that has remained a fantasy and unfortunately looks like it will remain so.
Besides a potential of a performance gain in parsing at the server side, I'm not quite sure there's much gain you would get - there might not be that much difference in serializing those data structures to "plain text" vs any other format you come up with.
Working with pure AST is a huge pain. One can trivially construct something that can not realistically be represented in an SQL dialect of choice.
LINQ is the (IMO) one of the best ways to do query composition, and with the nameof() operator you can really get away without a single hardcoded string in your application-level code.
Ehhh...something as simple as a left-outer join is near ungrokkable for me in LINQ. Cross apply / lateral join on a table-valued function? Forget about it.
Not to mention - due to the way LINQ works a lot of errors that seem like they should be caught at compile time don't get caught until run time (e.g. you wrote something in a where clause that can't be translated to SQL).
from c in categories
join p in products on c.Category equals p.Category into ps
from p in ps.DefaultIfEmpty()
select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName };
You're doing something called a 'group join' (second line) 'into' a new identifier (ps) above. I'm guessing it stuffs each matching result in 'p' into a list, which sorta makes sense. The second part mystifies me though - somehow 'from p in ps.DefaultifEmpty()' flattens the list back out into a denormalized result set while simultaneously preserving entries in 'c' with no matches.What does 'p' point to now? Why is it seemingly created twice? Is 'ps' still available? For a simple left join this is way, WAY too convoluted IMO.
The second thing I'm talking about you can read about by finding out when EF throws a 'NotSupportedException' at runtime. (e.g. https://stackoverflow.com/questions/25337974/entity-framewor...). Basically not everything you can do in LINQ can be translated to SQL. But it won't tell you at compile time. You have to wait till run time, which is garbage IMO. The example I linked is trivial but it's easy to miss this kind of thing when you're optimizing ORM use (and trying to move as much computation to the DB as possible).
This isn't usable for all sorts of all statements as, among other things, it's hard to maintain a stable API for all aspects. But might be useful for common cases. If there's more you need I'd be interested in a feature request! (I'm working in MySQL's clients team)
Consider SQL; it's declarative and highly expressive. Your users can craft queries that you did not consider, and you have to make sure they work consistently between versions. It's a very hard problem.
Creating more public APIs? It's a hard sell that vendors are sensitive to. Reducing the surface area makes it easier to add future improvements.
q)-3!parse"select last id from t"
"(?;`t;();0b;(,`id)!,(last;`id))"
Indeed you can use this to modify a user-entered query easily: p:parse"select last id from t";
@[p;2;'[1 rotate;,];enlist(=;`user;enlist u)]For those reading:
parse is a function that takes a string and returns the parse tree and -3! (gotta love APL) is execute that takes a tree and turns it back into a string.
The parsed syntax it spits out allows you to compose pieces of queries or modify them (there are easier ways such as queries return tables that can always be in inputs to other queries).
Maybe the author would like to add more context?
Oh no, please don't attack others like this in comments here. Even if you clearly mean well (which I'd say is the case here), it can only cause harm.
geocar's comments are consistently excellent (including for explaining the niceties of kdb and array languages to the community) and having fewer of them would make this place worse. Not everything needs to be spelled out, and besides, sometimes there just isn't time to do so. It can be a lot more work!
I did vote the comment up before responding to him as KDB, but I'm not sure why really.
A number of reasons from the PostgreSQL POV (work on it):
- our internal parser representation isn't stable and frequently changes from major release to major release, occasionally even in minor releases.
- we'd need a lot of additional verbose error checking code to make sure only sensible AST can be submitted
- there's two forms of parser AST "raw" and "analyzed". The former is produced without access to the database, the latter has done validity checks, type lookups, etc. The former is what you could validly produce remotely - but it's also more cumbersome to deal with.
I think there's arguments for supporting a useful different representation than SQL, but I think doing this with the already existing AST isn't going to fly. And a new proper language / representation would be a lot of work...
Edit: formatting
I don't think a new language would be needed, and most of the work has already been done in projects like LINQ. You'd just need to expose a stable AST front-end, which would be a lot easier than a whole new parser. I mean, it's just relational algebra right? ;-)
"Just". You still need a parser for that AST if it comes from the client, you need to convert that into the actual internal representation with validation and everything.
> I mean, it's just relational algebra right? ;-)
It's really not. A lot of SQL can't conveniently expressed in relational algebra (could luck with recursive CTEs) and a lot of other queries aren't as efficiently representable.
Yes, just: http://lambda-the-ultimate.org/node/5375
> A lot of SQL can't conveniently expressed in relational algebra (could luck with recursive CTEs) and a lot of other queries aren't as efficiently representable.
Clearly some language is being used to reason about these things, and evidently, this language is reflected in some SQL-like surface syntax. It would be no more a burden to provide an extensible AST than an extensible SQL-like language. In fact, it'd be considerably simpler since tokenizing, parsing and verification would all be simpler.
I've contributed to the H2 source code a few times, and in the process I got to learn much about how a database engine is implemented.
H2 home: http://www.h2database.com/
H2 Github repo: https://github.com/h2database/h2database
I attended a few years back and learnt an awful lot, this is the course page:
https://db.in.tum.de/teaching/ws1516/imlab/index.shtml?lang=...
Haven't found the accompanying slides yet, but they must be somewhere on their website.
What would be the rust equivalent of a relational language, the Go equivalent?
As an example, I had a lot of pleasure to read that blog : http://www.try-alf.org/blog/2014-12-03-what-would-a-function...
Last time I looked at benchmarks, sqlite4 with an LMDB backend was faster on average and had much lower variation in performance. Is that still the case?
Other 43.1 % 20.36 KB
Script 30.9 % 14.62 KB
Image 15.5 % 7.33 KB
CSS 5.3 % 2.52 KB
HTML 5.1 % 2.40 KB
Split out between: Image 25.0 % 2
Script 25.0 % 2
Other 25.0 % 2
HTML 12.5 % 1
CSS 12.5 % 1
So just 8 requests to build the page. 14.05 KB of the javascript is google analytics, so likely cached in the browser already.Nice, small, simple = quick :)
Being surprised about webspeed in 2017 sounds like heresy.
You might want to read some overviews on:
* postgres atomicity: https://brandur.org/postgres-atomicity
* postgres disk format: http://rachbelaid.com/introduction-to-postgres-physical-storage/
I have not read much on replication, though. Does anyone have any pointers?Sure, here you are:
void *x= malloc(16384)
(Couldn't resist)---
"Q: Why did the concurrent chicken cross the road?
A: the side other To to get"
(These were copied from the S.O. website)
For the format of the changes themselves there is continuum of different levels of abstraction that range from propagating the user's commands to slave servers directly (this is essentially what mysql does) to propagating commited writes to on-disk files (which is what default pgsql replication is about). Both extremes have their tradeoffs: in the SQL-level case the user has to make sure that results of transactions are deterministic (which is more complex than just not calling functions like rand() and now()) and in the physical page case all involved servers need to have exactly same on-disk layout (ie. same cpu architecture and you have to copy the actual on-disk files for initial synchronisation, dump and restore usually does not work)
For the actual propagation there are two approaches: write the change records somewhere and let the slaves read that at whatever pace they can or push the changes to slaves before the transaction is reported as commited to the client.
The full spectrum of PostgreSQL replication solutions in fact completely fills the space of these two choices and you probably should select carefully what matches your usecase best. (The two significant questions to ask is whether you can live with replicating the whole database cluster as one indivisible unit and whether you can live with possibly stale state on readonly replicas. When both answers are yes then streaming replication is what you want. If answer to second one is no, then you should probably redesign your application such that the data that requires this kind of guarantees is as small as possible)
What other build X from scratch would be needed to cover much of what we think a grad should know?
- build a language (compiler) from scratch
- build a graphics engine from scratch
- build a network stack (and firewall ?)
Err?
- build a simple virtual machine (pair with compiler)
- build a distributed storage system (perhaps like S3)
- build a traditional file system, perhaps as a FUSE (Filesystem in Userspace)
- build a job scheduler (bonus: distributed cluster work scheduler)
- build a memory allocator and/or a garbage collector
- build a web server
- build a crypto package (for learning only; don't really do this!)
For another MSc. level course the whole term project assignment was: "choose or design some language and try to somehow conjure compiler and if required VM for that". "somehow conjure" is significant because nobody said you have to do that from scratch, hacking tcc/gcc/lcc/llvm to accept your language was fine and basing your language on top of some Smalltalk VM (preferrably ST/X ;)) or Common Lisp was essentially encouraged.
... oh, nevermind, sorry, I failed to notice that you said coursework/internship, not portfolio.
I mean, not really, but it's a surprisingly viable starting point for simple problems. I've shipped it. Some production-grade software still uses mmap, albeit with a bunch of additional complexity to make sure it works okay in weird cases.
See this old comment thread I dug up that might be relevant: https://news.ycombinator.com/item?id=3982514
It seems like Redis might currently make use of mmap for some of its data, but I couldn't find an up-to-date source for that, just some old blog posts by the developer.
Sadly, about 50% of database design is trying to ensure the thing has a decent chance of starting up again if it crashes or loses power. This mostly consists of fighting file systems and disk caches.
Welcome to systems programming....
At one point, we ported it to a different Unix platform which had slightly different mmap semantics. It looked like everything worked, except for one minor detail: the data was never actually synced to disk. Ever. Until shutdown. Unfortunately, since the system ran 24/7, it effectively never synced. First time the system had a power failure, there was massive data loss on startup.
Oops.
(We were able to recover through log replay...)
I call that a win! What was the logging mechanism--something bespoke? What was your solution?
If you think something may benefit from a shared memory data store, lmdb may be worth considering as a fast, reliable, high concurrency alternative to reinventing wheels with raw mmap + manual sync.
Beginning with version 3.7.17 (2013-05-20), SQLite has the option of accessing disk content directly using memory-mapped I/O and the new xFetch() and xUnfetch() methods on sqlite3_io_methods. o you need to make an on-disk structure (i.e. btree) to let you search your tables (indices)
o that has to be fronted by an in memory cache
o your on-disk structure should maintain consistency even if the power fails and some writes get lost
- often but not always this is facilitated by keeping a separate write-optimized structure called a write-ahead-log
- if you use a WAL, you'll also need to implement the replay mechanisms to get the primary indices back up to date on a failure
o because the disk operations are expensive and high latency you'll need to start managing concurrency explicitly
back ends are alot of work. unfortunately this is a place where the lack of decent concurrency mechanisms in your language and OS interface can really cause alot of headache.pedagogically, i guess you would start with a completely synchronous non fault tolerant btree? or a maybe just the log, and introduce trees as a read accelerator?
this book looks to be pretty comprehensive, and explicitly discusses on-disk structures, write ahead logging, and recovery.
http://infolab.stanford.edu/~ullman/dscb.html
relational languages and transactions get alot more playin the academy..i think its because you can make sense of them in some abstract way without getting sucked into involved discussions about caching heuristics and OS write guarentees, scheduling and fussy performance characteristicsThe idea would be to map your memory space to disk space (using random access as provided by C stdlib); the problem would then be:
1. Caching - i guess you should then provide your cache implementation. And i would guess this opens a can of sync problems...
2. Finding a way to efficiently write the data on disk (i.e. which data should stay contiguous?; how much "slack" space should i leave on "extents" (oracle slang for a container for many data blocks)? Should you do column-store?
3. Compacting the data (removing slack)
etc etc.
I think it's not easy at all !
Alternative approach: For Windows, you could just use disk but pass FILE_ATTRIBUTE_TEMPORARY to CreateFile to force it to be in memory if the file is small enough.
https://github.com/elliotchance/c2go
> The goals of this project are:> .. The ultimate milestone is to be able to compile the SQLite3 source code and have it working without modification. This will be the 1.0.0 release.
So if that project does work out, there will be a Go SQLite implementation of sorts. No idea how messy/usable the result would be as a base for development though.
Note - haven't personally been tracking the project at all. More just that your statement prompted remembering it. :)
I would look into Berkeley DB (BDB) which is one of the most widely-used embedded databases, albeit not a SQL-based one. Otherwise take a look at the list of embedded databases on Wikipedia: https://en.wikipedia.org/wiki/Embedded_database
I'm not sure of the rationale; perhaps it's easier to keep the code / database files portable amongst different systems this way, and since it was designed for embedded use, many systems (and thus incompatibilites) were envisioned?
The screen zooms in and out as you scroll, so that when you scroll the text is a little too tiny and when you stop the text is cut off on the left edge.
I'm going to give it a try on my iPad. Just a heads up.
Edit: works fine on an iPad. Interesting.
See this explanation: http://sqlite.org/amalgamation.html
It's not like the authors spend a lot of time working on the amalgamation and that's the way the project is developed.
That's fig source. So, I guess this was made by some GUI that uses fig as its native file format. My guess would be xfig (http://mcj.sourceforge.net)
That said, it's one of the most reliable projects in existence so why rewrite it?
IIRC Mozilla's main gripe with it was the fact that there was only one implementation, and no standard beyond "Whatever Sqlite3 does".
If not: Redux works the same way for React Native as it does for React or anything else.