Skip to content

Commit 97181d1

Browse files
committed
Basic dbscheme generation from node-types.json
1 parent 735fde7 commit 97181d1

File tree

4 files changed

+2390
-1
lines changed

4 files changed

+2390
-1
lines changed

generator/src/dbscheme.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/// Represents a distinct entry in the database schema.
2+
pub enum Entry {
3+
/// An entry defining a database table.
4+
Table(Table),
5+
6+
/// An entry defining type that is a union of other types.
7+
Union { name: String, members: Vec<String> },
8+
}
9+
10+
/// A table in the database schema.
11+
pub struct Table {
12+
pub name: String,
13+
pub columns: Vec<Column>,
14+
pub keysets: Vec<Vec<String>>,
15+
}
16+
17+
/// A column in a table.
18+
pub struct Column {
19+
pub db_type: DbColumnType,
20+
pub name: String,
21+
pub unique: bool,
22+
pub ql_type: QlColumnType,
23+
pub ql_type_is_ref: bool,
24+
}
25+
26+
/// The database column type.
27+
pub enum DbColumnType {
28+
Int,
29+
String,
30+
}
31+
32+
// The QL type of a column.
33+
pub enum QlColumnType {
34+
/// Primitive `int` type.
35+
Int,
36+
37+
/// Primitive `string` type.
38+
String,
39+
40+
/// A custom type, defined elsewhere by a table or union.
41+
Custom(String),
42+
}
43+
44+
const RESERVED_KEYWORDS: [&'static str; 14] = [
45+
"boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype",
46+
"type", "unique", "varchar",
47+
];
48+
49+
/// Returns a string that's a copy of `name` but suitably escaped to be a valid
50+
/// QL identifier.
51+
pub fn escape_name(name: &str) -> String {
52+
let mut result = String::new();
53+
54+
// If there's a leading underscore, replace it with 'underscore_'.
55+
if let Some(c) = name.chars().next() {
56+
if c == '_' {
57+
result.push_str("underscore");
58+
}
59+
}
60+
for c in name.chars() {
61+
match c {
62+
'{' => result.push_str("lbrace"),
63+
'}' => result.push_str("rbrace"),
64+
'<' => result.push_str("langle"),
65+
'>' => result.push_str("rangle"),
66+
'[' => result.push_str("lbracket"),
67+
']' => result.push_str("rbracket"),
68+
'(' => result.push_str("lparen"),
69+
')' => result.push_str("rparen"),
70+
'|' => result.push_str("pipe"),
71+
'=' => result.push_str("equal"),
72+
'~' => result.push_str("tilde"),
73+
'?' => result.push_str("question"),
74+
'`' => result.push_str("backtick"),
75+
'^' => result.push_str("caret"),
76+
'!' => result.push_str("bang"),
77+
'#' => result.push_str("hash"),
78+
'%' => result.push_str("percent"),
79+
'&' => result.push_str("ampersand"),
80+
'.' => result.push_str("dot"),
81+
',' => result.push_str("comma"),
82+
'/' => result.push_str("slash"),
83+
':' => result.push_str("colon"),
84+
';' => result.push_str("semicolon"),
85+
'"' => result.push_str("dquote"),
86+
'*' => result.push_str("star"),
87+
'+' => result.push_str("plus"),
88+
'-' => result.push_str("minus"),
89+
'@' => result.push_str("at"),
90+
_ => result.push_str(&c.to_lowercase().to_string()),
91+
}
92+
}
93+
94+
for &keyword in &RESERVED_KEYWORDS {
95+
if result == keyword {
96+
result.push_str("__");
97+
break;
98+
}
99+
}
100+
101+
result
102+
}
103+
104+
/// Generates the dbscheme by writing the given dbscheme `entries` to the `file`.
105+
pub fn write(file: &mut dyn std::io::Write, entries: &[Entry]) -> Result<(), std::io::Error> {
106+
write!(file, "// CodeQL database schema for Ruby\n")?;
107+
write!(
108+
file,
109+
"// Automatically generated from the tree-sitter grammar; do not edit\n\n"
110+
)?;
111+
112+
for entry in entries {
113+
match entry {
114+
Entry::Table(table) => {
115+
for keyset in &table.keysets {
116+
write!(file, "#keyset[")?;
117+
for (key_index, key) in keyset.iter().enumerate() {
118+
if key_index > 0 {
119+
write!(file, ", ")?;
120+
}
121+
write!(file, "{}", key)?;
122+
}
123+
write!(file, "]\n")?;
124+
}
125+
126+
write!(file, "{}(\n", table.name)?;
127+
for (column_index, column) in table.columns.iter().enumerate() {
128+
write!(file, " ")?;
129+
if column.unique {
130+
write!(file, "unique ")?;
131+
}
132+
write!(
133+
file,
134+
"{} ",
135+
match column.db_type {
136+
DbColumnType::Int => "int",
137+
DbColumnType::String => "string",
138+
}
139+
)?;
140+
write!(file, "{}: ", column.name)?;
141+
match &column.ql_type {
142+
QlColumnType::Int => write!(file, "int")?,
143+
QlColumnType::String => write!(file, "string")?,
144+
QlColumnType::Custom(name) => write!(file, "@{}", name)?,
145+
}
146+
if column.ql_type_is_ref {
147+
write!(file, " ref")?;
148+
}
149+
if column_index + 1 != table.columns.len() {
150+
write!(file, ",")?;
151+
}
152+
write!(file, "\n")?;
153+
}
154+
write!(file, ");\n\n")?;
155+
}
156+
Entry::Union { name, members } => {
157+
write!(file, "@{} = ", name)?;
158+
let mut first = true;
159+
for member in members {
160+
if first {
161+
first = false;
162+
} else {
163+
write!(file, " | ")?;
164+
}
165+
write!(file, "@{}", member)?;
166+
}
167+
write!(file, "\n\n")?;
168+
}
169+
}
170+
}
171+
172+
Ok(())
173+
}

0 commit comments

Comments
 (0)