commit 04cd92df45449a323caea686b99fb63f89fd74d0
parent 2aa6f7b445e5a07c1a799a5c2211b83016b40f0a
Author: quantumish <freifeld.david@gmail.com>
Date: Fri, 22 Mar 2024 17:40:04 -0400
Add further temporal query support, more sample data, and install utils
Diffstat:
| M | install.sh | | | 1 | + |
| M | minisql.sml | | | 58 | ++++++++++++++++++++++++++++++++++++++-------------------- |
| M | randgen.py | | | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- |
| A | viewer.py | | | 11 | +++++++++++ |
4 files changed, 129 insertions(+), 47 deletions(-)
diff --git a/install.sh b/install.sh
@@ -2,6 +2,7 @@ wget https://smlnj.org/dist/working/110.99.5/config.tgz
mkdir smlnj
tar -xf config.tgz -C smlnj
cd smlnj && config/install.sh && cd ..
+wget -O words.txt https://github.com/InnovativeInventor/dict4schools/raw/master/safedict_full.txt
export PATH=$(pwd)/smlnj/bin:$PATH
ml-build minisql.cm Main.main minisql-image
diff --git a/minisql.sml b/minisql.sml
@@ -6,8 +6,8 @@ type row = JSON.value
type rowvals = JSON.value list
datatype value = String of string | Int of int | Column of columnref |
- CurColumn of columnref | Time of int
-datatype binop = Eq of (value * value) | Gt of (value * value)
+ CurColumn of columnref | Time of int | Sub of value * value
+datatype binop = Eq of value * value | Gt of value * value
datatype pred = Expr of binop | And of pred * pred | Or of pred * pred |
Not of pred | Next of pred | Until of pred * pred | True
datatype targets = Columns of columnref list | All
@@ -31,6 +31,7 @@ fun StrongRelease (x, y) = Not(WeakUntil(Not x, Not y))
fun Within (x, 0) = x
| Within (x, i) = Or(Next(x), Next(Within(x, i-1)))
+
(*--------------------------- Parsing logic. -----------------------------*)
structure P = ParserComb
@@ -70,17 +71,21 @@ fun parseTime getc = P.or'([
P.wrap((Int.scan StringCvt.DEC) @> P.string "weeks", fn x => Time(x*60*60*24*7))
]) getc
-fun parseValue getc = P.or'([
+fun parseSimpleValue getc = P.or'([
P.wrap((P.eatChar isQuote) *> P.token (fn x => not (isQuote x))
*> (P.eatChar isQuote), (String o middle)),
parseTime,
P.wrap(Int.scan StringCvt.DEC, Int),
P.wrap(P.string "cur." *> parseRef, CurColumn o #2),
- P.wrap(parseRef, Column)
+ P.wrap(parseRef, Column)
]) getc
+and parseValue getc = P.or(
+ P.wrap(parseSimpleValue +> P.char #"-" %> parseSimpleValue, Sub),
+ parseSimpleValue
+ ) getc
fun parseExpr getc = P.or'([
- P.wrap(parseValue +> P.char #"=" %> parseValue, Expr o Eq),
+ P.wrap(parseValue +> P.char #"=" %> parseValue, Expr o Eq),
P.wrap(parseValue +> P.char #">" %> parseValue, Expr o Gt),
P.wrap(parseValue +> P.char #"<" %> parseValue, Lt),
P.wrap(parseValue +> P.string "!=" %> parseValue, Neq)
@@ -137,15 +142,19 @@ fun jsonEqual (JSON.INT x) (JSON.INT y) = x = y
| jsonEqual (JSON.STRING x) (JSON.STRING y) = x = y
| jsonEqual _ _ = raise Type
+fun colInt row c = JSONUtil.asInt (getCol row c)
+fun colStr row c = JSONUtil.asString (getCol row c)
+
fun equal row x y cur =
if x = y then true else
(case (x, y) of
((String _), (Int _)) => raise Type
- | ((Int x), (Column c)) => x = JSONUtil.asInt (getCol row c)
- | ((Time x), (Column c)) => x = JSONUtil.asInt (getCol row c)
- | ((String x), (Column c)) => x = JSONUtil.asString (getCol row c)
+ | ((Int x), (Column c)) => x = colInt row c
+ | ((Time x), (Column c)) => x = colInt row c
+ | ((String x), (Column c)) => x = colStr row c
| ((Column c1), (Column c2)) => jsonEqual (getCol row c1) (getCol row c2)
| ((CurColumn c1), (Column c2)) => jsonEqual (getCol cur c1) (getCol row c2)
+ | ((Time x), (Time y)) => x = y (* sml doesn't recognize Time as an eq type? *)
| ((CurColumn c1), _) => raise Type
| ((Time _), _) => raise Type
| (x,y) => equal row y x cur)
@@ -156,24 +165,33 @@ fun greater row x y cur =
(case (x, y) of
((Int x), (Int y)) => x > y
| ((Time x), (Time y)) => x > y
- | ((Int x), (Column c)) => x > JSONUtil.asInt (getCol row c)
- | ((Column c), (Int x)) => JSONUtil.asInt (getCol row c) > x
- | ((Time x), (Column c)) => x > JSONUtil.asInt (getCol row c)
- | ((Column c), (Time x)) => JSONUtil.asInt (getCol row c) > x
- | ((Column c1), (Column c2)) => JSONUtil.asInt (getCol row c1) > JSONUtil.asInt (getCol row c2)
- | ((CurColumn c1), (Column c2)) => JSONUtil.asInt (getCol cur c1) > JSONUtil.asInt (getCol row c2)
- | ((Column c1), (CurColumn c2)) => JSONUtil.asInt (getCol row c1) > JSONUtil.asInt (getCol cur c2)
+ | ((Int x), (Column c)) => x > (colInt row c)
+ | ((Column c), (Int x)) => (colInt row c) > x
+ | ((Time x), (Column c)) => x > (colInt row c)
+ | ((Column c), (Time x)) => (colInt row c) > x
+ | ((Column c1), (Column c2)) => (colInt row c1) > (colInt row c2)
+ | ((CurColumn c1), (Column c2)) => (colInt cur c1) > (colInt row c2)
+ | ((Column c1), (CurColumn c2)) => (colInt row c1) > (colInt cur c2)
| _ => raise Type)
handle Option => raise BadQuery
-
+
+fun eval (Sub(x, y)) row cur =
+ (case (x,y) of
+ (Column(c1), Column(c2)) => Time((colInt row c1) - (colInt row c2))
+ | (CurColumn(c1), CurColumn(c2)) => Time((colInt cur c1) - (colInt cur c2))
+ | (Column(c1), CurColumn(c2)) => Time((colInt row c1) - (colInt cur c2))
+ | (CurColumn(c1), Column(c2)) => Time((colInt cur c1) - (colInt row c2))
+ | _ => raise Type)
+ | eval x _ _ = x
+
(* Given a predicate and a row, check if the row satisfies the predicate. *)
fun check p row (start, future) = let
val st = Option.getOpt (start, row)
val S = (start, future)
in
(case (p, future) of
- (Expr(Eq(x, y)), _) => equal row x y st
- | (Expr(Gt(x, y)), _) => greater row x y st
+ (Expr(Eq(x, y)), _) => equal row (eval x row st) (eval y row st) st
+ | (Expr(Gt(x, y)), _) => greater row (eval x row st) (eval y row st) st
| (And(p1, p2), ft) => check p1 row S andalso check p2 row S
| (Or(p1, p2), ft) => check p1 row S orelse check p2 row S
| (Not(p), f) => not (check p row S)
@@ -216,8 +234,8 @@ fun execute (Select(req, table, pred, lim)) : rowvals list * columnref list =
(List.map (fn row => List.map (fn c => getCol row c) targets)
(optTake lim (predFilter pred (r::rs))),
targets)
-end (* handle Match => raise MalformedData *)
- (* | Io => raise NoSuchFile *)
+end handle Match => raise MalformedData
+ | Io => raise NoSuchFile
(*--------------------------- Display utilities. -----------------------------*)
diff --git a/randgen.py b/randgen.py
@@ -1,30 +1,82 @@
-
-# { "state": "Mexico", "region": "South", "pop": 2312312322, "pop_male": 3123123, "pop_female": 123123 }
import json
+import sys
import random
import urllib.request
-states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']
-regions = ["South", "West", "Southwest", "North", "East", "Northeast", "Middle", "Midwest", "Mideast", "Nowhere"]
-
-word_site = "https://www.mit.edu/~ecprice/wordlist.10000"
-response = urllib.request.urlopen(word_site)
-txt = response.read()
-WORDS = txt.splitlines()
-
-print(random.choice(states))
-
-# print(WORDS[:10])
-
-data = []
-for i in range(10000):
- data.append({"state": random.choice(states),
- "region": random.choice(regions),
- "name": f"{random.choice(WORDS).decode().capitalize()} {random.choice(WORDS).decode().capitalize()}",
- "pop": random.randint(1000000, 1000000000),
- "pop_male": random.randint(10000, 1000000),
- "pop_female": random.randint(10000, 1000000)})
-
-f = open("./cities.json", "w")
-f.write(json.dumps(data))
-f.close()
-
+import time
+
+WORDS = [l.strip() for l in open("./words.txt").readlines()]
+
+def cities():
+ states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']
+ regions = ["South", "West", "Southwest", "North", "East", "Northeast", "Middle", "Midwest", "Mideast", "Nowhere"]
+
+
+ print(random.choice(states))
+
+ # print(WORDS[:10])
+
+ data = []
+ for i in range(10000):
+ data.append({"state": random.choice(states),
+ "region": random.choice(regions),
+ "name": f"{random.choice(WORDS).capitalize()} {random.choice(WORDS).capitalize()}",
+ "pop": random.randint(1000000, 1000000000),
+ "pop_male": random.randint(10000, 1000000),
+ "pop_female": random.randint(10000, 1000000)})
+
+ f = open("./cities.json", "w")
+ f.write(json.dumps(data))
+ f.close()
+
+class Customer:
+ def __init__(self):
+ self.past_actions = []
+ self.name = f"{random.choice(WORDS).capitalize()} Corp."
+ self.headcount = random.randint(10, 100000)
+
+ def next(self):
+ self.headcount = random.randint(10, 100000)
+ if self.past_actions == []:
+ self.past_actions.append("new")
+ return "new"
+ possible = []
+ if "new" in self.past_actions:
+ if "buy" not in self.past_actions:
+ self.past_actions.append("buy")
+ return "buy"
+ if "buy" in self.past_actions:
+ if "return" not in self.past_actions:
+ self.past_actions.append(random.choice(["return", "complain"]))
+ else:
+ self.past_actions.append("leave")
+ return self.past_actions[-1]
+
+def customers(seq=True):
+ data = []
+ actions = ["buy", "new", "return", "complain", "leave"]
+ users = [Customer() for i in range(100)]
+ t = int(time.time())
+ for i in range(1000):
+ t += random.randint(600, 604800 * 3)
+ user = random.choice(users)
+ entry = {}
+ if not seq:
+ entry["time"] = t
+ entry.update({ "action": user.next(),
+ "name": user.name,
+ "headcount": user.headcount })
+ data.append(entry)
+ if data[-1]["action"] == "leave":
+ users.remove(user)
+ users.append(Customer())
+
+ f = open(f"./{'seq' if seq else ''}users.json", "w")
+ f.write(json.dumps(data))
+ f.close()
+
+if sys.argv[1] == "cities":
+ cities()
+elif sys.argv[1] == "sequsers":
+ customers()
+elif sys.argv[1] == "users":
+ customers(seq=False)
diff --git a/viewer.py b/viewer.py
@@ -0,0 +1,11 @@
+import json
+import sys
+import pkg_resources
+
+installed = {pkg.key for pkg in pkg_resources.working_set}
+if "rich" in installed:
+ from rich import print
+
+f = json.loads(open(sys.argv[1]).read())
+for i in range(int(sys.argv[2])):
+ print(f[i])