A set of new database projects including those listed here are intended to bring significant advances to the database software industry in general, in a manner expressing both idealism and pragmatism; see GitHub (https://github.com/muldis) for the latest versions.
Specifications In Progress:Implementations for the following languages are intended afterwards: Perl, Raku, ECMAScript, Ruby, Python, PHP, Rust, Go, Swift, others.
THE REST OF THE DOCUMENTATION BELOW THIS LINE IS OUTDATED TO VARYING DEGREES AND WILL BE BROUGHT UP LATER. MEANWHILE THE GITHUB REPOSITORIES NAMED ABOVE SHOULD BE CONSIDERED TO HAVE THE MOST RECENT DOCUMENTATION AND CODE.
Muldis Data Language is an industrial-strength computationally complete high-level application programming language with fully integrated database functionality; you can use it to define, query, and update ("object") relational databases, as well as write general purpose applications. The language's paradigm is a mixture of declarative, homoiconic, functional, imperative, and object-oriented. Muldis Data Language is currently under development.
See the end of this page for a complete simple "hello world" program, which interacts with the user.
See https://github.com/muldis for the latest Muldis Data Language specification and implementations (some outdated) as it has so far been written. In particular see https://github.com/muldis/Muldis-Data-Language.
For all intents and purposes you can consider Muldis Data Language to be what SQL could have been; it can express anything useful that SQL can, but in a much improved manner. So it should be easier to write more expressive and less kludgy code in Muldis Data Language than in SQL. The following simple code comparisons (and aforementioned code examples) demonstrate this:
Muldis Data Language | SQL (parenthesis means as a scalar query) |
---|---|
"x" |
(SELECT 'x') (SELECT 'x' FROM dual) |
tab1 |
SELECT DISTINCT * FROM tab1 |
#tab1 |
(SELECT COUNT(DISTINCT *) FROM tab1) |
tab1 on \$(col1,col2) tab1 %= \$(col1,col2) tab1 π \$(col1,col2) |
SELECT DISTINCT col1, col2 FROM tab1 |
Input: tab(col1,col2,col3,bigcol4,col5,col6,col7,col8)
tab but \bigcol4 tab %- \bigcol4 |
SELECT DISTINCT col1, col2, col3, col5, col6, col7, col8 FROM tab |
tab1 matching \?%{ ( col1 : "hello", col2 : 5 ), ( col1 : "world", col2 : 7 ), } tab1 ⋉ \?%{ ( col1 : "hello", col2 : 5 ), ( col1 : "world", col2 : 7 ), } |
TODO: UPDATE TO USE VALUES CLAUSE HERE, REVISIT OTHER SQL FOR ANY CLEANER VERSIONS. SELECT DISTINCT * FROM tab1 WHERE (col1, col2) IN ( SELECT 'hello' AS col1, 5 AS col2 UNION SELECT 'world' AS col1, 7 AS col2 ) SELECT DISTINCT * FROM tab1 WHERE col1 = 'hello' AND col2 = 5 OR col1 = 'world' AND col2 = 7 |
Inputs: tab1(col1,col2,col3), tab2(col2,col3,col4)
Result: tab(col1,col2,col3,col4) tab1 join tab2 tab1 ⋈ tab2 |
SELECT DISTINCT * FROM tab1 NATURAL INNER JOIN tab2 SELECT DISTINCT * FROM tab1 INNER JOIN tab2 USING (col2, col3) SELECT DISTINCT tab1.*, tab2.col4 FROM tab1 INNER JOIN tab2 ON tab2.col2 = tab1.col2 AND tab2.col3 = tab1.col3 |
Input: tab1(col1,col2,col3)
tab1 rename \$:(col1->foo,col3->bar) tab1 $:= \$:(col1->foo,col3->bar) tab1 ρ \$:(col1->foo,col3->bar) |
SELECT DISTINCT col1 AS foo, col2, col3 AS bar FROM tab1 |
Summarize by named attributes giving count:
if ?people then ( people map \(( group : args:.\0 %= \$(age,ctry), member : args:.\0 )) pipe group map \( args:.\0:.\group %+ ( count_by_age_ctry : #args:.\0:.\members, )) ) else \?%( age, ctry, count_by_age_ctry ) |
SELECT age, ctry, COUNT(DISTINCT *) AS count_by_age_ctry FROM people GROUP BY age, ctry |
if ?sales then ( sales map \( sale ::= args:.\0; returns ( group : sale:.\region, member : sale ); ) pipe group map \( region ::= args:.\0:.\group; sales_for_region ::= args:.\0:.\members; revenue ::= sales_for_region attr_to_Bag \price reduce \plus::(); qty ::= sales_for_region attr_to_Bag \units reduce \plus::(); avg_price_per_unit ::= revenue / qty; returns ( :region, :revenue, :qty, :avg_price_per_unit ); ) ) else \?%( region, revenue, qty, avg_price_per_unit ) |
SELECT region, SUM(price) AS revenue, SUM(units) AS qty, AVG(price/units) AS avg_price_per_unit, FROM sales GROUP BY region |
count := #(people:& where \( person ::= args:.\0; returns person:.\name = "John" and (person:.\title = "Mr" or person:.\abbrev = "Dr") )); count := #(people:& σ \( person ::= args:.\0; returns person:.\name = "John" ∧ (person:.\title = "Mr" ∨ (person:.\abbrev = "Dr")) )); |
SELECT COUNT(DISTINCT *) INTO (count) FROM people WHERE name = 'John' AND ( title = 'Mr' OR abbrev = 'Dr' ) |
foo := bar:&; |
START TRANSACTION; SET CONSTRAINTS ALL DEFERRED; TRUNCATE foo; INSERT INTO foo SELECT DISTINCT * FROM bar; SET CONSTRAINTS ALL IMMEDIATE; COMMIT; |
foo := foo:& union bar:&; foo := foo:& ∪ bar:&; |
INSERT INTO foo (SELECT DISTINCT * FROM bar EXCEPT SELECT * FROM foo) |
foo := foo:& union (\$:(col1<-,col2<-) renaming \?%{ (1,3), (2,4) }); foo := foo:& ∪ (\$:(col1<-,col2<-) renaming \?%{ (1,3), (2,4) }); |
INSERT INTO foo ( col1, col2 ) VALUES ( 1, 3 ), ( 2, 4 ) |
foo := foo:& not_matching \?%{(col1:10,),(col1:20,)}); foo := foo:& ⊿ \?%{(col1:10,),(col1:20,)}); |
DELETE FROM foo WHERE col1 = 10 OR col1 = 20 |
foo := foo:& update (col1 : 1, col2 : 6); |
UPDATE foo SET col1 = 1, col2 = 6 |
Muldis Data Language provides an effective means to reduce or avoid entirely the problem of "object-relational impedance mismatch". The primary way that Muldis Data Language deals with the above problem is that it natively supports user-defined types at the database level, which can be arbitrarily complex such as to support collection values as attribute values. And so users can effectively use their same type definitions in both the database and in their application, with no non-trivial mapping required. This is in contrast with typical database ORM tools which only remap application objects into simpler built-in database types like plain numbers and strings.
The syntax and feature set of Muldis Data Language is like that of a general purpose programming language, and so shouldn't be too difficult to learn. Multiple other languages influence its design in various ways, not just SQL but also a variety of general purpose application languages, such as Raku.
In contrast to a typical SQL DBMS which uses multiple programming languages together, where SQL is used for queries and something else (possibly SQL-alike) for defining stored procedures, a Muldis Data Language DBMS would use the same Muldis Data Language language to assume both roles. Moreover, Muldis Data Language can also be used to write your main application, if you wanted to; in fact, this kind of usage is encouraged for many common cases of simple database-using web applications.
See https://muldis.com/CD.html a simple (outdated) example of actual Muldis Data Language code, which defines both a database schema and some routines which query or update the database. The example is derived from the "simple CD database example" that comes with the manual/cookbook of the DBIx::Class Perl module. The example is essentially complete, with scaffolding, such that you could almost just run it as is through a Muldis Data Language compiler/interpreter and it would run.
Muldis Data Language is intended for at least 2 primary uses; in the long term, it is intended to supplant SQL as the access language of choice for relational DBMS vendors in their products; in the short term, it is intended to be the most effective intermediate representation for code that either is intended to have SQL (or a host application language in the case of less capable or privilege-restricted DBMSs, including non-relational or "No-SQL" ones) generated from it, or that SQL would be parsed into it, and so it would be the tool of choice for making widely-DBMS-portable schemas and applications, that leverage a high fraction of a DBMSs native features rather than a lowest common denominator, or for porting such; moreover, it is meant to abstract away where code actually runs, so an implementation can choose to either have parts of it run on a server or on a client (similarly to how tools like LLVM can transparently make code run in the CPU or the GPU, as is best or is even possible at the time). The intermediate representation usage is supported in particular by Muldis Data Language having both regular string forms, for when you manually write code, as well as host language data structure forms, for when you are generating code, good for preventing "SQL injection attacks"; and being homoiconic, Muldis Data Language can manipulate and run Muldis Data Language code at runtime.
Muldis Data Language is an Acmeist programming language for writing portable database modules, that work with any DBMS and with any other programming language, for superior database interoperability.
THIS DOCUMENTATION SECTION IS OUTDATED.
While the specification of Muldis Data Language is essentially complete and so can mostly be implemented as is, no Muldis Data Language implementation actually exists yet. A complete reference implementation is expected in the next year or so, but it isn't here yet. Actually, at this point it is expected that the reference implementation (now under development) will lead the spec in the near future, in that it will implement a substantially updated Muldis Data Language syntax and standard library, before the specification then catches up to it. But despite this, Muldis Data Language still can be put to work for you today, in a limited fashion.
Practical benefits that you can have right now include:
Use Muldis Data Language as a more-expressive prototyping language; you can write out your database-concerning code in Muldis Data Language first to get a good feel for your database schema and queries, and then write it out in analogous SQL afterwards when you have your planning worked out.
Makers of SQL-generating toolkits (such as the Perl modules Fey or SQL::Abstract or Rose::DB::Object::QueryBuilder) can use Muldis Data Language's Perl data structure form as a guide to improve their APIs so that their users can then express what they want more easily and cleanly, while gaining SQL's full feature-set without having to write any string-SQL snippets, because Muldis Data Language's coverage is fairly thorough. Similarly, various database access toolkits (such as DBIx::Class or SQL::Translator or DBIx::Perlish) or ORMs can be updated to use the code representation that Muldis Data Language specs as their internal representation, or provide some of that in their API, as they go about their database-mapping duties.
Also, such toolkits can, when used to deploy or update a generated database schema, also deploy a lot of their now-more-portable query code or data-manipulation code as database stored routines, so that there is less fretting and contrivance at normal application runtime where they try to load user client code lazily or conditionally for performance because it would be expensively regenerated every time; the greater use of database stored procedures would mean that more of the relevant code is pre-compiled and there is less of it in always-regenerated form to fuss about on the client side.
Note that, while Muldis Data Language should be able to represent and map to/from any database schema, it works best with a well-designed schema. In this way I have made the same decision as some other database tools. If your schema is well-designed, it should map cleanly, make beautiful code, and perform well; if your schema is badly designed, it would map more roughly, with more kludgey looking code, and performance can suffer. Muldis Data Language is designed intentionally such that good practices are given the easiest path to them, and poor practices have a less-desirable path. Focusing on supporting good designs is better for sanity and for keeping the bug count down; trying to make poor designs perform well is like trying to make a program that returns the right results when given the wrong inputs. An example good practice is writing generic queries with bind parameters, which can be pre-compiled, rather than writing a separate data-specific query for each database invocation.
Designers of other programming languages, especially ones still under active design and development such as Raku, can look at Muldis Data Language and see if any of its ostensibly novel design aspects or features might influence them as they evolve their own languages. For example, how it actually is practical and useful to have relational types and operators integrated into your language as much as the likes of arrays, sets, and dictionaries/hashes are.
THIS DOCUMENTATION SECTION IS OUTDATED.
Currently fully-specified features include:
Currently partially-specified features include:
Currently mostly-unspecified but intended features include:
THIS DOCUMENTATION SECTION IS OUTDATED.
While essentially complete, the Muldis Data Language language specification is officially considered "pre-alpha" quality, because it is mostly untested in execution. (Except for an (outdated) adapted subset, by the Set::Relation Perl module, which does execute now.)
The next major milestone for Muldis Data Language will be when a working self-contained reference implementation of the language has been made, in the form of a pure-Perl DBMS, Muldis::D::RefEng. At this point, both the language and said implementation will officially be considered "alpha" quality.
Following that, perhaps the next major milestone will include any of having a decent TAP-based test suite, or having multiple implementations including a Parrot-hosted language, or implementations over SQL DBMSs (starting with PostgreSQL, which seems to be the most conducive environment for it, considering its existing good feature set, solid quality, freedom, and its developers' priorities; other early hosting targets include Sybase, DB2, Oracle, and SQLite, where Perl or another HLL will be used to support stored procedures for the latter), at which point the language can officially be considered "beta" quality.
Note that Muldis Data Language is designed such that it can be mostly self-hosted (written in itself) meaning once you have one functional implementation, you have most of another one, and the only reason to be less self-hosted is to aid performance, rather than just getting it to work at all; MREE will be written in that way.
Muldis Data Language and its official implementations are commercially supported by Muldis Data Systems.
If you want a "homepage" url to link to, you can use https://muldis.com concerning this project or particularly its commercial support.
And https://github.com/muldis is its main public GIT version control repository.
Here's a bonus Muldis Data Language example, a complete simple program that first gathers info on people from the user, and then lets the user search that information; it only uses lexical variables.
(\Syntax:([\Muldis_D_Plain_Text, "https://muldis.com", [0,300,0]]: (\Model:([\Muldis_Data_Language, "https://muldis.com", [0,300,0]]: (\Package : ( identity : ( package_base_name : [\My_App], authority : "https://mycorp.com", version_number : "0", ), foundation : ( authority : "https://muldis.com", version_number : "0", ), uses : ( MD : ( package_base_name : [\Core], authority : "https://muldis.com", version_number : "0", ), ), entry : \$package::main, floating : {\$package, \$used::MD}, materials : ( main : (\Procedure : ( matches : (), performs : [ `This is the program's main procedure.` write_Text_line( "Hello!" ); declare people: \?%(name,addr); declare name: ""; declare addr: ""; write_Text_line( "First add some info. Enter blank line when done." ); gather_person block [ `Gathers person info from user.` prompt_Text_line( name, "\Enter a person\as name: " ); if name:& = "" then leave gather_person; prompt_Text_line( addr, "\Enter that person\as address: " ); if addr:& = "" then leave gather_person; `Adds a person to our db of people.` people := people:& insert (:&name,:&addr); iterate gather_person; ]; write_Text_line( "Now search for info. Enter blank line when done." ); lookup_person block [ prompt_Text_line( name, "Enter a name to search for: " ); if name:& = "" then leave lookup_person; `Look up person by name, get their address.` write_Text_line( given #(matched_people ::= people:& matching (:&name,)) when 0 then "No person matched" when 1 then guard "Found address for that person is: " ~ (%matched_people):.\addr default "More than one person matched" ); iterate lookup_person; ]; write_Text_line( "Goodbye!" ); ], )), ), )) )) ))
Copyright © 2007-2023, Muldis Data Systems, Inc.
https://muldis.com
MULDIS and MULDIS MULTIVERSE OF DISCOURSE are trademarks of Muldis Data Systems, Inc.