├── .gitignore ├── README.md ├── nb-configuration.xml ├── pom.xml └── src ├── main └── java │ └── kis │ └── sqlparser │ ├── App.java │ ├── Column.java │ ├── Context.java │ ├── Index.java │ ├── ModifiedTuple.java │ ├── ObjectPatternMatch.java │ ├── Pair.java │ ├── Schema.java │ ├── SqlAnalizer.java │ ├── SqlParser.java │ ├── StreamUtils.java │ ├── Table.java │ └── Transaction.java └── test └── java └── kis └── sqlparser ├── AppTest.java ├── IndexTest.java ├── SqlAnalizerTest.java ├── SqlParserTest.java └── StreamUtilsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sqlparser 2 | ========= 3 | 4 | tiny sql parser 5 | 6 | execute kis.sqlparser.SqlAnalizer 7 | 8 | Table tshohin = new Table("shohin", Stream.of("id", "name", "bunrui_id", "price") 9 | .map(s -> new Column(s)).collect(Collectors.toList())); 10 | Table tbunrui = new Table("bunrui", Stream.of("id", "name", "seisen") 11 | .map(s -> new Column(s)).collect(Collectors.toList())); 12 | tbunrui 13 | .insert(1, "野菜", 1) 14 | .insert(2, "くだもの", 1) 15 | .insert(3, "菓子", 2); 16 | tshohin 17 | .insert(1, "りんご", 2, 250) 18 | .insert(2, "キャベツ", 1, 200) 19 | .insert(3, "たけのこの", 3, 150) 20 | .insert(4, "きのこ", 3, 120) 21 | .insert(5, "パソコン", 0, 34800); 22 | 23 | result 24 | 25 | select id, name from shohin where price between 130 and 200 or id=1 26 | 初期プラン:select 27 | <- filter[between shohin.price:130:200 or shohin.id = 1] 28 | <- table[shohin] 29 | 論理最適化:select 30 | <- filter[between shohin.price:130:200 or shohin.id = 1] 31 | <- table[shohin] 32 | [1,りんご] 33 | [2,キャベツ] 34 | [3,たけのこの] 35 | 36 | select id, name from shohin where price between 130 and 200 37 | 初期プラン:select 38 | <- filter[between shohin.price:130:200] 39 | <- table[shohin] 40 | 論理最適化:select 41 | <- filter[between shohin.price:130:200] 42 | <- table[shohin] 43 | [2,キャベツ] 44 | [3,たけのこの] 45 | 46 | 普通のJOIN 47 | select shohin.id, shohin.name,bunrui.name 48 | from shohin left join bunrui on shohin.bunrui_id=bunrui.id 49 | 初期プラン:select 50 | <- join(nested loop) 51 | <- table[shohin] 52 | / 53 | <- table[bunrui] 54 | 論理最適化:select 55 | <- join(nested loop) 56 | <- table[shohin] 57 | / 58 | <- table[bunrui] 59 | [1,りんご,くだもの] 60 | [2,キャベツ,野菜] 61 | [3,たけのこの,菓子] 62 | [4,きのこ,菓子] 63 | [5,パソコン,null] 64 | 65 | optimize 66 | 67 | 常に真なので条件省略 68 | select id, name from shohin where 2 < 3 69 | 初期プラン:select 70 | <- filter[2 < 3] 71 | <- table[shohin] 72 | 論理最適化:select 73 | <- table[shohin] 74 | [1,りんご] 75 | [2,キャベツ] 76 | [3,たけのこの] 77 | [4,きのこ] 78 | [5,パソコン] 79 | 80 | 常に偽なので空になる 81 | select id, name from shohin where price < 130 and 2 > 3 82 | 初期プラン:select 83 | <- filter[shohin.price < 130 and 2 > 3] 84 | <- table[shohin] 85 | 論理最適化:select 86 | <- empty 87 | <- table[shohin] 88 | 89 | メインテーブルのみに関係のある条件はJOINの前に適用 90 | select shohin.id, shohin.name,bunrui.name 91 | from shohin left join bunrui on shohin.bunrui_id=bunrui.id 92 | where shohin.price <= 300 and bunrui.seisen=1 93 | 初期プラン:select 94 | <- filter[shohin.price <= 300 and bunrui.seisen = 1] 95 | <- join(nested loop) 96 | <- table[shohin] 97 | / 98 | <- table[bunrui] 99 | 論理最適化:select 100 | <- filter[bunrui.seisen = 1] 101 | <- join(nested loop) 102 | <- filter[shohin.price <= 300] 103 | <- table[shohin] 104 | / 105 | <- table[bunrui] 106 | [1,りんご,くだもの] 107 | [2,キャベツ,野菜] 108 | 109 | 110 | -------------------------------------------------------------------------------- /nb-configuration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | JDK_1.8 17 | 18 | 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | kis 6 | sqlparser 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | sqlparser 11 | http://maven.apache.org 12 | 13 | 14 | 15 | org.apache.maven.plugins 16 | maven-compiler-plugin 17 | 2.3.2 18 | 19 | 1.8 20 | 1.8 21 | 22 | 23 | 24 | 25 | 26 | UTF-8 27 | 28 | 29 | 30 | 31 | junit 32 | junit 33 | 4.13.1 34 | test 35 | 36 | 37 | jparsec 38 | jparsec 39 | 2.0.1 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | 1.12.2 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/App.java: -------------------------------------------------------------------------------- 1 | package kis.sqlparser; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App 8 | { 9 | public static void main( String[] args ) 10 | { 11 | System.out.println( "Hello World!" ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Column.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.Optional; 10 | import lombok.AllArgsConstructor; 11 | import lombok.EqualsAndHashCode; 12 | import lombok.ToString; 13 | 14 | /** 15 | * 16 | * @author naoki 17 | */ 18 | @AllArgsConstructor 19 | @EqualsAndHashCode 20 | @ToString 21 | public class Column { 22 | Optional parent; 23 | String name; 24 | 25 | public Column(Table parent, String name){ 26 | this(Optional.ofNullable(parent), name); 27 | } 28 | 29 | public Column(String name){ 30 | this(Optional.empty(), name); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Context.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.Optional; 10 | import java.util.function.Consumer; 11 | import kis.sqlparser.SqlAnalizer.Records; 12 | import kis.sqlparser.Table.Tuple; 13 | 14 | /** 15 | * 16 | * @author naoki 17 | */ 18 | public class Context { 19 | Schema schema; 20 | Optional currentTx; 21 | 22 | public Context(Schema schema) { 23 | this.schema = schema; 24 | currentTx = Optional.empty(); 25 | } 26 | 27 | public Records exec(String sql){ 28 | return SqlAnalizer.exec(this, sql); 29 | } 30 | 31 | public void begin(){ 32 | if(currentTx.isPresent()){ 33 | throw new RuntimeException("transaction already exist."); 34 | } 35 | currentTx = Optional.of(schema.createTransaction()); 36 | } 37 | public void commit(){ 38 | currentTx.orElseThrow(() -> new RuntimeException("transaciton does not begin")) 39 | .commit(); 40 | end(); 41 | } 42 | public void abort(){ 43 | currentTx.orElseThrow(() -> new RuntimeException("transaciton does not begin")) 44 | .abort(); 45 | end(); 46 | } 47 | void end(){ 48 | currentTx = Optional.empty(); 49 | } 50 | 51 | public void withTx(Consumer cons){ 52 | Transaction tx = currentTx.orElseGet(() -> schema.createTransaction()); 53 | if(!tx.enable){ 54 | throw new RuntimeException("transaction is not enabled."); 55 | } 56 | cons.accept(tx); 57 | 58 | if(!currentTx.isPresent()){ 59 | tx.commit(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Index.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.Iterator; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.SortedMap; 17 | import java.util.TreeMap; 18 | import kis.sqlparser.Table.Tuple; 19 | import lombok.AllArgsConstructor; 20 | 21 | /** 22 | * 23 | * @author naoki 24 | */ 25 | @AllArgsConstructor 26 | public abstract class Index { 27 | 28 | public Map> tuples; 29 | int targetColumn; 30 | 31 | public void insert(Tuple tuple){ 32 | withKey(tuple).ifPresent(key -> 33 | tuples.computeIfAbsent(key, k -> new ArrayList<>()).add(tuple)); 34 | } 35 | public void delete(Tuple tuple){ 36 | withKey(tuple).ifPresent(key -> 37 | tuples.computeIfPresent(key, (k, v) -> { 38 | v.remove(tuple); 39 | return v.isEmpty() ? null : v; 40 | })); 41 | } 42 | public void update(List> old, Tuple newTuple){ 43 | Optional newKey = withKey(newTuple); 44 | if(old.size() > targetColumn || old.get(targetColumn).isPresent()){ 45 | if(newKey.isPresent()){ 46 | if(old.get(targetColumn).get().equals(newKey.get())){ 47 | return; 48 | } 49 | } 50 | tuples.computeIfPresent(old.get(targetColumn).get(), (k, v) -> { 51 | for(Iterator ite = v.iterator(); ite.hasNext();){ 52 | Tuple t = ite.next(); 53 | if(t.rid == newTuple.rid) ite.remove(); 54 | } 55 | return v.isEmpty() ? null : v; 56 | }); 57 | } 58 | insert(newTuple); 59 | } 60 | 61 | Optional withKey(Tuple tuple){ 62 | if(tuple.row.size() <= targetColumn || !tuple.row.get(targetColumn).isPresent()){ 63 | return Optional.empty(); 64 | } 65 | return tuple.row.get(targetColumn); 66 | } 67 | 68 | public Iterator equalsTo(Object value){ 69 | return tuples.getOrDefault(value, Collections.EMPTY_LIST).iterator(); 70 | } 71 | 72 | public static class HashIndex extends Index{ 73 | public HashIndex(int columnIndex){ 74 | super(new LinkedHashMap<>(), columnIndex); 75 | } 76 | } 77 | public static class TreeIndex extends Index{ 78 | public TreeIndex(int targetColumn) { 79 | super(new TreeMap<>(), targetColumn); 80 | } 81 | public Iterator between(Object from, Object to){ 82 | return flatten(((TreeMap>)tuples).subMap(from, true, to, true)); 83 | } 84 | public Iterator compare(Object v, String op){ 85 | TreeMap> tm = (TreeMap>)tuples; 86 | SortedMap> m = 87 | "<".equals(op) ? tm.headMap(v, false) : 88 | "<=".equals(op) ? tm.headMap(v, true) : 89 | ">".equals(op) ? tm.tailMap(v, false) : 90 | ">=".equals(op) ? tm.tailMap(v, true) : 91 | (SortedMap>)Collections.EMPTY_MAP; 92 | return flatten(m); 93 | } 94 | 95 | Iterator flatten(SortedMap> m){ 96 | return m.values().stream() 97 | .flatMap(ts -> ts.stream()).iterator(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/ModifiedTuple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import kis.sqlparser.Table.TableTuple; 10 | 11 | /** 12 | * 13 | * @author naoki 14 | */ 15 | public abstract class ModifiedTuple { 16 | TableTuple oldtuple; 17 | long modiryTx; 18 | long commitTx; 19 | 20 | public ModifiedTuple(TableTuple oldtuple, long modiryTx) { 21 | this.oldtuple = oldtuple; 22 | this.modiryTx = modiryTx; 23 | this.commitTx = 0; 24 | } 25 | 26 | public static class Deleted extends ModifiedTuple{ 27 | public Deleted(TableTuple oldtuple, long modiryTx) { 28 | super(oldtuple, modiryTx); 29 | } 30 | 31 | @Override 32 | public void execAbort() { 33 | oldtuple.table.dataInsert(oldtuple); 34 | } 35 | } 36 | public static class Updated extends ModifiedTuple{ 37 | TableTuple newTuple; 38 | public Updated(TableTuple oldtuple, TableTuple newTuple, long modiryTx) { 39 | super(oldtuple, modiryTx); 40 | this.newTuple = newTuple; 41 | } 42 | 43 | @Override 44 | public void execAbort() { 45 | oldtuple.table.dataUpdate(oldtuple, newTuple); 46 | } 47 | } 48 | 49 | public void abort(){ 50 | oldtuple.modified = false; 51 | execAbort(); 52 | } 53 | public abstract void execAbort(); 54 | 55 | public boolean isCommited(){ 56 | return commitTx != 0; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/ObjectPatternMatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.function.Consumer; 10 | import java.util.function.Function; 11 | import java.util.function.Supplier; 12 | import lombok.AllArgsConstructor; 13 | 14 | /** 15 | * 16 | * @author naoki 17 | */ 18 | public class ObjectPatternMatch { 19 | public interface MatchCase{ 20 | boolean match(Object o); 21 | } 22 | 23 | @AllArgsConstructor 24 | public static class CaseOf implements MatchCase{ 25 | Class cls; 26 | Consumer proc; 27 | 28 | @Override 29 | public boolean match(Object o){ 30 | if(!cls.isInstance(o)){ 31 | return false; 32 | } 33 | proc.accept((T)o); 34 | return true; 35 | } 36 | } 37 | 38 | public static void match(Object o, MatchCase... cos){ 39 | for(MatchCase co : cos){ 40 | if(co.match(o)) return; 41 | } 42 | //throw new RuntimeException("no match"); 43 | } 44 | public static CaseOf caseOf(Class cls, Consumer proc){ 45 | return new CaseOf<>(cls, proc); 46 | } 47 | public static MatchCase noMatch(Runnable proc){ 48 | return o -> {proc.run(); return true;}; 49 | } 50 | 51 | 52 | public interface MatchCaseRet{ 53 | boolean match(Object o); 54 | R proc(Object o); 55 | } 56 | @AllArgsConstructor 57 | public static class CaseOfRet implements MatchCaseRet{ 58 | Class cls; 59 | Function func; 60 | @Override 61 | public boolean match(Object o) { 62 | return cls.isInstance(o); 63 | } 64 | 65 | @Override 66 | public R proc(Object o) { 67 | return func.apply((T)o); 68 | } 69 | } 70 | 71 | public static R matchRet(Object o, MatchCaseRet... cors){ 72 | for(MatchCaseRet cor : cors){ 73 | if(cor.match(o)) return cor.proc(o); 74 | } 75 | return null; 76 | } 77 | 78 | public static CaseOfRet caseOfRet(Class cls, Function func){ 79 | return new CaseOfRet<>(cls, func); 80 | } 81 | public static MatchCaseRet noMatchRet(Supplier sup){ 82 | return new MatchCaseRet() { 83 | 84 | @Override 85 | public boolean match(Object o) { 86 | return true; 87 | } 88 | 89 | @Override 90 | public R proc(Object o) { 91 | return sup.get(); 92 | } 93 | }; 94 | } 95 | public static MatchCaseRet noMatchThrow(Supplier sup){ 96 | return new MatchCaseRet() { 97 | 98 | @Override 99 | public boolean match(Object o) { 100 | return true; 101 | } 102 | 103 | @Override 104 | public R proc(Object o) { 105 | throw sup.get(); 106 | } 107 | }; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Pair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.function.BiConsumer; 10 | import java.util.function.BiFunction; 11 | import java.util.function.Function; 12 | import lombok.AllArgsConstructor; 13 | 14 | /** 15 | * 16 | * @author naoki 17 | */ 18 | @AllArgsConstructor 19 | public class Pair { 20 | T left; 21 | U right; 22 | 23 | public static Pair of(M left, N right){ 24 | return new Pair<>(left, right); 25 | } 26 | public void with(BiConsumer cons){ 27 | cons.accept(left, right); 28 | } 29 | public R reduce(BiFunction func){ 30 | return func.apply(left, right); 31 | } 32 | public Pair map(Function lfunc, Function rfunc){ 33 | return of(lfunc.apply(left), rfunc.apply(right)); 34 | } 35 | public Pair flatMap(BiFunction> func){ 36 | return func.apply(left, right); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Schema.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Iterator; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Optional; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * 19 | * @author naoki 20 | */ 21 | public class Schema { 22 | Map tables; 23 | long txId; 24 | LinkedList trans; 25 | 26 | public Schema() { 27 | this(new ArrayList<>()); 28 | } 29 | 30 | public Schema(List
tables){ 31 | txId = 0; 32 | this.tables = tables.stream() 33 | .collect(Collectors.toMap(t -> t.name, t -> t)); 34 | this.trans = new LinkedList<>(); 35 | } 36 | 37 | public Optional
find(String name){ 38 | return Optional.ofNullable(tables.get(name)); 39 | } 40 | 41 | public Context createContext(){ 42 | return new Context(this); 43 | } 44 | public Transaction createTransaction(){ 45 | ++txId; 46 | Transaction tx = new Transaction(this, txId); 47 | trans.add(tx); 48 | return tx; 49 | } 50 | 51 | public void removeFinTx(){ 52 | for(Iterator ite = trans.iterator(); ite.hasNext();){ 53 | Transaction tx = ite.next(); 54 | if(tx.enable) break; 55 | tx.removeModified(); 56 | ite.remove(); 57 | } 58 | } 59 | 60 | public void removeTx(Transaction tx){ 61 | trans.remove(tx); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/SqlAnalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.time.LocalDateTime; 10 | import java.time.format.DateTimeFormatter; 11 | import java.util.AbstractMap; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.HashMap; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Optional; 19 | import java.util.function.UnaryOperator; 20 | import static java.util.stream.Collectors.toList; 21 | import static java.util.stream.Collectors.toMap; 22 | import static java.util.stream.Collectors.joining; 23 | import java.util.stream.IntStream; 24 | import java.util.stream.Stream; 25 | import static kis.sqlparser.ObjectPatternMatch.*; 26 | import static kis.sqlparser.StreamUtils.zip; 27 | import kis.sqlparser.SqlParser.*; 28 | import kis.sqlparser.Table.Tuple; 29 | import kis.sqlparser.Table.TableTuple; 30 | import lombok.AllArgsConstructor; 31 | import lombok.EqualsAndHashCode; 32 | import lombok.ToString; 33 | import org.codehaus.jparsec.Parser; 34 | 35 | /** 36 | * 37 | * @author naoki 38 | */ 39 | public class SqlAnalizer { 40 | public static interface SqlValue{// extends ASTExp{ 41 | 42 | } 43 | @AllArgsConstructor @EqualsAndHashCode 44 | public static class BooleanValue implements SqlValue{ 45 | boolean value; 46 | 47 | @Override 48 | public String toString() { 49 | return value + ""; 50 | } 51 | } 52 | @EqualsAndHashCode 53 | public static class NullValue implements SqlValue{ 54 | @Override 55 | public String toString() { 56 | return "NULL"; 57 | } 58 | } 59 | 60 | @AllArgsConstructor 61 | public static class BinaryOp implements SqlValue{ 62 | SqlValue left; 63 | SqlValue right; 64 | String op; 65 | 66 | @Override 67 | public String toString() { 68 | return String.format("%s %s %s", left, op, right); 69 | } 70 | } 71 | 72 | @AllArgsConstructor 73 | public static class FieldValue implements SqlValue{ 74 | Column column; 75 | 76 | @Override 77 | public String toString() { 78 | return column.parent.map(t -> t.name + ".").orElse("") + column.name; 79 | } 80 | } 81 | 82 | @AllArgsConstructor 83 | public static class FieldIndex implements SqlValue{ 84 | int idx; 85 | 86 | @Override 87 | public String toString() { 88 | return "Field." + idx; 89 | } 90 | } 91 | 92 | @AllArgsConstructor 93 | public static class TernaryOp implements SqlValue{ 94 | SqlValue cond; 95 | SqlValue first; 96 | SqlValue sec; 97 | String op; 98 | 99 | @Override 100 | public String toString() { 101 | return String.format("%s %s:%s:%s", op, cond, first, sec); 102 | } 103 | } 104 | @ToString 105 | public static abstract class FunctionExp implements SqlValue{ 106 | String name; 107 | List params; 108 | 109 | public FunctionExp(String name, List params, int paramCount) { 110 | this.name = name; 111 | this.params = params; 112 | if(params.size() != paramCount){ 113 | throw new RuntimeException(name + " function requires " + paramCount + " param"); 114 | } 115 | } 116 | 117 | } 118 | public static abstract class GeneralFuncExp extends FunctionExp{ 119 | public GeneralFuncExp(String name, List params, int count) { 120 | super(name, params, count); 121 | } 122 | 123 | abstract SqlValue eval(Map colIndex, Tuple tuple); 124 | } 125 | public static class LengthFunc extends GeneralFuncExp{ 126 | public LengthFunc(List params) { 127 | super("length", params, 1); 128 | } 129 | 130 | @Override 131 | SqlValue eval(Map colIndex, Tuple tuple) { 132 | SqlValue result = SqlAnalizer.eval(params.get(0), colIndex, tuple); 133 | if(result instanceof StringValue){ 134 | return new IntValue(((StringValue)result).value.length()); 135 | } 136 | throw new RuntimeException(result.getClass() + " is not supported for length()"); 137 | } 138 | } 139 | public static class NowFunc extends GeneralFuncExp{ 140 | 141 | public NowFunc(List params) { 142 | super("now", params, 0); 143 | } 144 | 145 | @Override 146 | SqlValue eval(Map colIndex, Tuple tuple) { 147 | return new StringValue(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"))); 148 | } 149 | 150 | } 151 | public static abstract class AggregationExp extends FunctionExp{ 152 | public AggregationExp(String name, List param, int paramCount) { 153 | super(name, param, paramCount); 154 | } 155 | abstract void accept(Map colIndex, Tuple tuple); 156 | abstract SqlValue getValue(); 157 | abstract void reset(); 158 | } 159 | 160 | public static class CountExp extends AggregationExp{ 161 | int count; 162 | 163 | public CountExp(List param) { 164 | super("count", param, 1); 165 | count = 0; 166 | } 167 | @Override 168 | void accept(Map colIndex, Tuple tuple) { 169 | SqlValue result = eval(params.get(0), colIndex, tuple); 170 | if(!(result instanceof NullValue)){ 171 | ++count; 172 | } 173 | } 174 | 175 | @Override 176 | void reset() { 177 | count = 0; 178 | } 179 | 180 | @Override 181 | SqlValue getValue() { 182 | return new IntValue(count); 183 | } 184 | } 185 | 186 | public static class SumExp extends AggregationExp{ 187 | int total; 188 | 189 | public SumExp(List params) { 190 | super("sum", params, 1); 191 | total = 0; 192 | } 193 | @Override 194 | void accept(Map colIndex, Tuple tuple) { 195 | SqlValue result = eval(params.get(0), colIndex, tuple); 196 | if(result instanceof IntValue){ 197 | total += ((IntValue)result).value; 198 | }else if(result instanceof NullValue){ 199 | }else{ 200 | throw new RuntimeException(result.getClass() + " is not supported for sum()"); 201 | } 202 | } 203 | 204 | @Override 205 | void reset() { 206 | total = 0; 207 | } 208 | 209 | @Override 210 | SqlValue getValue() { 211 | return new IntValue(total); 212 | } 213 | 214 | } 215 | 216 | public static SqlValue wrap(Optional o){ 217 | if(!o.isPresent()){ 218 | return new NullValue(); 219 | } 220 | return matchRet(o.get(), 221 | caseOfRet(String.class, s -> new StringValue(s)), 222 | caseOfRet(Integer.class, i -> new IntValue(i)), 223 | caseOfRet(Boolean.class, b -> new BooleanValue(b)), 224 | noMatchThrow(() -> new RuntimeException(o.getClass() + " is not supported")) 225 | ); 226 | } 227 | 228 | public static Optional unwrap(SqlValue v){ 229 | return matchRet(v, 230 | caseOfRet(StringValue.class, s -> Optional.of(s.value)), 231 | caseOfRet(IntValue.class, i -> Optional.of(i.value)), 232 | caseOfRet(BooleanValue.class, b -> Optional.of(b.value)), 233 | caseOfRet(NullValue.class, n -> Optional.empty()), 234 | noMatchThrow(() -> new RuntimeException(v.getClass() + " is not supported")) 235 | ); 236 | 237 | } 238 | 239 | public static SqlValue eval(SqlValue value, Map colIndex, Tuple tuple){ 240 | return matchRet(value, 241 | caseOfRet(StringValue.class, s -> s), 242 | caseOfRet(BooleanValue.class, b -> b), 243 | caseOfRet(IntValue.class, i -> i), 244 | caseOfRet(NullValue.class, n -> n), 245 | caseOfRet(GeneralFuncExp.class, f -> f.eval(colIndex, tuple)), 246 | caseOfRet(AggregationExp.class, a -> a.getValue()), 247 | caseOfRet(FieldValue.class, f -> { 248 | int idx = colIndex.getOrDefault(f.column, -1); 249 | if(idx < 0) throw new RuntimeException(f.column + " not found."); 250 | if(idx >= tuple.row.size()) return new NullValue(); 251 | return wrap(tuple.row.get(idx)); 252 | }), 253 | caseOfRet(FieldIndex.class, f -> { 254 | if(f.idx >= tuple.row.size()) return new NullValue(); 255 | return wrap(tuple.row.get(f.idx)); 256 | }), 257 | caseOfRet(BinaryOp.class, bin -> { 258 | SqlValue left = eval(bin.left, colIndex, tuple); 259 | SqlValue right = eval(bin.right, colIndex, tuple); 260 | if(left instanceof NullValue || right instanceof NullValue){ 261 | return new NullValue(); 262 | } 263 | String op = bin.op; 264 | if("and".equals(op) || "or".equals(op)){ 265 | if(left instanceof BooleanValue && right instanceof BooleanValue){ 266 | switch(op){ 267 | case "and": 268 | return new BooleanValue(((BooleanValue)left).value & ((BooleanValue)right).value); 269 | case "or": 270 | return new BooleanValue(((BooleanValue)left).value | ((BooleanValue)right).value); 271 | } 272 | }else{ 273 | throw new RuntimeException(op + " operator need boolean value"); 274 | } 275 | } 276 | if(left instanceof IntValue && right instanceof IntValue){ 277 | int ileft = ((IntValue)left).value; 278 | int iright = ((IntValue)right).value; 279 | boolean ret; 280 | switch(op){ 281 | case "=": 282 | ret = ileft == iright; 283 | break; 284 | case "<": 285 | ret = ileft < iright; 286 | break; 287 | case ">": 288 | ret = ileft > iright; 289 | break; 290 | case "<=": 291 | ret = ileft <= iright; 292 | break; 293 | case ">=": 294 | ret = ileft >= iright; 295 | break; 296 | default:{ 297 | int iret; 298 | switch(op){ 299 | case "+": 300 | iret = ileft + iright; 301 | break; 302 | case "-": 303 | iret = ileft - iright; 304 | break; 305 | case "*": 306 | iret = ileft * iright; 307 | break; 308 | case "/": 309 | iret = ileft / iright; 310 | break; 311 | default: 312 | throw new RuntimeException("[" +op + "] operator is not supported"); 313 | } 314 | return new IntValue(iret); 315 | } 316 | } 317 | return new BooleanValue(ret); 318 | }else{ 319 | throw new RuntimeException(op + " operator need int value"); 320 | } 321 | }), 322 | caseOfRet(TernaryOp.class, ter ->{ 323 | if(!"between".equals(ter.op)){ 324 | throw new RuntimeException(ter.op + " is not supported"); 325 | } 326 | SqlValue cond = eval(ter.cond, colIndex, tuple); 327 | SqlValue first = eval(ter.first, colIndex, tuple); 328 | SqlValue sec = eval(ter.sec, colIndex, tuple); 329 | if(cond instanceof NullValue || first instanceof NullValue || sec instanceof NullValue){ 330 | return new NullValue(); 331 | }else if(cond instanceof IntValue && first instanceof IntValue && sec instanceof IntValue){ 332 | int icond = ((IntValue)cond).value; 333 | int ifirst = ((IntValue)first).value; 334 | int isec = ((IntValue)sec).value; 335 | return new BooleanValue((ifirst <= icond && icond <= isec) || (isec <= icond && icond <= ifirst)); 336 | }else{ 337 | throw new RuntimeException("between operator need int value"); 338 | } 339 | }), 340 | noMatchThrow(() -> new RuntimeException(value.getClass() + " is not suppoerted")) 341 | ); 342 | } 343 | 344 | public static List findField(Map env, String name){ 345 | return env.values().stream() 346 | .flatMap(t -> t.columns.stream()) 347 | .filter(c -> c.name.equals(name)) 348 | .collect(toList()); 349 | } 350 | 351 | public static abstract class QueryPlan{ 352 | abstract List getColumns(); 353 | abstract Records records(); 354 | Optional to; 355 | 356 | public QueryPlan() { 357 | this.to = Optional.empty(); 358 | } 359 | 360 | public void setTo(NodePlan to) { 361 | this.to = Optional.ofNullable(to); 362 | } 363 | 364 | Map getColumnIndex(){ 365 | return StreamUtils.zip(getColumns().stream(), Stream.iterate(0, i -> i + 1)) 366 | .collect(toMap(p -> p.left, p -> p.right)); 367 | } 368 | 369 | } 370 | //@AllArgsConstructor 371 | public static abstract class NodePlan extends QueryPlan{ 372 | QueryPlan from; 373 | public NodePlan(QueryPlan from) { 374 | this.from = from; 375 | from.setTo(this); 376 | } 377 | } 378 | public static abstract class ColumnThroughPlan extends NodePlan{ 379 | public ColumnThroughPlan(QueryPlan from) { 380 | super(from); 381 | } 382 | @Override 383 | List getColumns() { 384 | return from.getColumns(); 385 | } 386 | } 387 | static class Counter{ 388 | int count; 389 | Counter(){ 390 | count = 0; 391 | } 392 | int getCount(){ 393 | return ++count; 394 | } 395 | } 396 | 397 | @FunctionalInterface 398 | static interface Records{ 399 | Optional next(); 400 | } 401 | 402 | public static class SelectPlan extends NodePlan{ 403 | List values; 404 | 405 | public SelectPlan(QueryPlan from, List values){ 406 | super(from); 407 | this.values = values; 408 | } 409 | @Override 410 | List getColumns() { 411 | Counter c = new Counter(); 412 | return values.stream().flatMap(v -> 413 | v instanceof ASTWildcard ? 414 | from.getColumns().stream() : 415 | Stream.of((v instanceof FieldValue) ? 416 | ((FieldValue)v).column : 417 | new Column(c.getCount() + ""))) 418 | .collect(toList()); 419 | } 420 | 421 | @Override 422 | Records records() { 423 | Records records = from.records(); 424 | Map columnIndex = from.getColumnIndex(); 425 | 426 | return () -> { 427 | Optional line = records.next(); 428 | if(!line.isPresent()) return line; 429 | 430 | List> row = values.stream().flatMap(c -> { 431 | if(c instanceof ASTWildcard){ 432 | return from.getColumns().stream().map(FieldValue::new); 433 | }else{ 434 | return Stream.of(c); 435 | } 436 | }) 437 | .map(c -> eval(c, columnIndex, line.get())) 438 | .map(v -> unwrap(v)) 439 | .collect(toList()); 440 | return Optional.of(new Tuple(line.get().rid, row)); 441 | }; 442 | } 443 | 444 | @Override 445 | public String toString() { 446 | return "select\n <- " + from.toString(); 447 | } 448 | 449 | } 450 | 451 | public static class FilterPlan extends ColumnThroughPlan{ 452 | List conds; 453 | 454 | public FilterPlan(QueryPlan from, List conds) { 455 | super(from); 456 | this.conds = conds; 457 | } 458 | public FilterPlan(QueryPlan from, SqlValue cond) { 459 | this(from, Arrays.asList(cond)); 460 | } 461 | 462 | @Override 463 | Records records() { 464 | Records records = from.records(); 465 | Map columnIndex = from.getColumnIndex(); 466 | 467 | return () -> { 468 | for(Optional optLine; (optLine = records.next()).isPresent();){ 469 | Tuple line = optLine.get(); 470 | boolean allTrue = conds.stream() 471 | .map(cond -> eval(cond, columnIndex, line)) 472 | .allMatch(val -> val instanceof BooleanValue && ((BooleanValue)val).value); 473 | if(allTrue){ 474 | return optLine; 475 | } 476 | } 477 | return Optional.empty(); 478 | }; 479 | } 480 | 481 | @Override 482 | public String toString() { 483 | return "filter" + conds + "\n <- " + from.toString(); 484 | } 485 | 486 | } 487 | 488 | public static class EmptyPlan extends ColumnThroughPlan{ 489 | 490 | public EmptyPlan(QueryPlan from) { 491 | super(from); 492 | } 493 | 494 | @Override 495 | Records records() { 496 | return () -> Optional.empty(); 497 | } 498 | 499 | @Override 500 | public String toString() { 501 | return "empty\n <- " + from.toString(); 502 | } 503 | } 504 | 505 | public static class TablePlan extends QueryPlan{ 506 | Table table; 507 | Optional tx; 508 | 509 | public TablePlan(Optional tx, Table table) { 510 | this.table = table; 511 | this.tx = tx; 512 | } 513 | 514 | @Override 515 | List getColumns() { 516 | return table.columns; 517 | } 518 | 519 | @Override 520 | Records records() { 521 | Iterator ite = table.data.values().iterator(); 522 | return () -> { 523 | while(ite.hasNext()) { 524 | TableTuple tuple = ite.next(); 525 | if(tx.isPresent()){ 526 | //トランザクションがある 527 | if(!tx.get().tupleAvailable(tuple)) continue; 528 | }else{ 529 | //トランザクションがない 530 | if(!tuple.isCommited()) continue; 531 | } 532 | //if(tx.isPresent() && ) 533 | return Optional.of(tuple); 534 | } 535 | return Optional.empty(); 536 | }; 537 | } 538 | 539 | @Override 540 | public String toString() { 541 | return "table[" + table.name + "]"; 542 | } 543 | } 544 | 545 | public static class HistoryPlan extends QueryPlan{ 546 | Optional tx; 547 | Table table; 548 | 549 | public HistoryPlan(Optional tx, Table table) { 550 | this.table = table; 551 | this.tx = tx; 552 | } 553 | 554 | @Override 555 | List getColumns() { 556 | return table.columns; 557 | } 558 | 559 | @Override 560 | Records records() { 561 | Iterator ite = table.getModifiedTuples(tx).iterator(); 562 | return () -> 563 | ite.hasNext() ? Optional.of(ite.next()) : Optional.empty(); 564 | } 565 | } 566 | 567 | public static class UnionPlan extends NodePlan{ 568 | QueryPlan secondPlan; 569 | 570 | public UnionPlan(QueryPlan firstPlan, QueryPlan secondPlan) { 571 | super(firstPlan); 572 | this.secondPlan = secondPlan; 573 | } 574 | 575 | @Override 576 | List getColumns() { 577 | return from.getColumns(); 578 | } 579 | 580 | @Override 581 | Records records() { 582 | List> recs = Arrays.asList( 583 | from.records(), secondPlan.records()); 584 | int[] idx = {0}; 585 | return () -> { 586 | while(idx[0] < recs.size()){ 587 | Optional rec = recs.get(idx[0]).next(); 588 | if(rec.isPresent()) return rec; 589 | ++idx[0]; 590 | } 591 | return Optional.empty(); 592 | }; 593 | } 594 | } 595 | 596 | public static class OrderPlan extends ColumnThroughPlan{ 597 | List> order; 598 | 599 | public OrderPlan(QueryPlan from, List> order) { 600 | super(from); 601 | this.order = order; 602 | } 603 | 604 | @Override 605 | Records records() { 606 | List>>> tuples = new ArrayList<>(); 607 | Records rec = from.records(); 608 | Map columnIndex = getColumnIndex(); 609 | for(Optional line; (line = rec.next()).isPresent();){ 610 | Tuple t = line.get(); 611 | tuples.add(Pair.of(t, 612 | order.stream().map(p -> p.map(v -> eval(v, columnIndex, t), b -> b)) 613 | .collect(toList()))); 614 | } 615 | tuples.sort((o1, o2) -> { 616 | for(Iterator> itel =o1.right.iterator(), iter = o2.right.iterator(); itel.hasNext() && iter.hasNext();){ 617 | Pair l = itel.next(); 618 | Pair r = iter.next(); 619 | if(l.left instanceof NullValue){ 620 | if(r.left instanceof NullValue){ 621 | continue; 622 | } 623 | return 1; 624 | } 625 | if(r.left instanceof NullValue){ 626 | return -1; 627 | } 628 | if(r.left instanceof IntValue && l.left instanceof IntValue){ 629 | int ret = Integer.compare(((IntValue)l.left).value, ((IntValue)r.left).value); 630 | if(ret == 0) continue; 631 | return l.right ? ret : -ret; 632 | } 633 | if(r.left instanceof StringValue && l.left instanceof StringValue){ 634 | int ret = ((StringValue)l.left).value.compareTo(((StringValue)r.left).value); 635 | if(ret == 0) continue; 636 | return l.right ? ret : -ret; 637 | } 638 | } 639 | return 0; 640 | }); 641 | Iterator>>> ite = tuples.iterator(); 642 | return () -> { 643 | return ite.hasNext() ? Optional.of(ite.next().left) : Optional.empty(); 644 | }; 645 | } 646 | 647 | @Override 648 | public String toString() { 649 | return "order[] <- " + from.toString(); 650 | } 651 | 652 | } 653 | 654 | public static class JoinPlan extends NodePlan{ 655 | QueryPlan secondary; 656 | SqlValue cond; 657 | 658 | public JoinPlan(QueryPlan from, QueryPlan secondary, SqlValue cond) { 659 | super(from); 660 | this.secondary = secondary; 661 | this.cond = cond; 662 | } 663 | @Override 664 | List getColumns() { 665 | return Stream.concat( 666 | from.getColumns().stream(), 667 | secondary.getColumns().stream()).collect(toList()); 668 | } 669 | 670 | @Override 671 | Records records() { 672 | Records records = from.records(); 673 | Map columnIndex = getColumnIndex(); 674 | return () -> { 675 | Optional optLine = records.next(); 676 | if(!optLine.isPresent()) return optLine; 677 | Tuple line = optLine.get(); 678 | Records srec = secondary.records(); 679 | for(Optional sline;(sline = srec.next()).isPresent();){ 680 | Tuple joinline = new Tuple(0, Stream.concat( 681 | line.row.stream(), sline.get().row.stream()) 682 | .collect(toList())); 683 | SqlValue v = eval(cond, columnIndex, joinline); 684 | if(v instanceof BooleanValue && ((BooleanValue)v).value){ 685 | return Optional.of(joinline); 686 | } 687 | } 688 | return Optional.of(new Tuple(0, Stream.concat( 689 | Stream.concat( 690 | line.row.stream(), 691 | IntStream.range(0, getColumns().size() - line.row.size()).mapToObj(i -> Optional.empty())), 692 | IntStream.range(0, secondary.getColumns().size()).mapToObj(i -> Optional.empty())) 693 | .collect(toList()))); 694 | }; 695 | } 696 | 697 | @Override 698 | public String toString() { 699 | return "join(nested loop)\n <- " + from.toString() + "\n /\n <- " + secondary.toString(); 700 | } 701 | 702 | } 703 | public static void gatherAggregation(List aggs, SqlValue v){ 704 | match(v, 705 | caseOf(GeneralFuncExp.class, f -> { 706 | f.params.forEach(p -> gatherAggregation(aggs, p)); 707 | }), 708 | caseOf(BinaryOp.class, bin -> { 709 | gatherAggregation(aggs, bin.left); 710 | gatherAggregation(aggs, bin.right); 711 | }), 712 | caseOf(TernaryOp.class, ter -> { 713 | gatherAggregation(aggs, ter.cond); 714 | gatherAggregation(aggs, ter.first); 715 | gatherAggregation(aggs, ter.sec); 716 | }), 717 | caseOf(AggregationExp.class, agg ->{ 718 | aggs.add(agg); 719 | }) 720 | ); 721 | } 722 | static final UnaryOperator INC = i -> i + 1; 723 | public static class AggregationPlan extends NodePlan{ 724 | List group; 725 | List fields; 726 | public AggregationPlan(QueryPlan from, List fields, List group) { 727 | super(from); 728 | this.fields = fields; 729 | this.group = group; 730 | } 731 | 732 | @Override 733 | List getColumns() { 734 | return zip(fields.stream(), Stream.iterate(1, INC)) 735 | .map(p -> p.reduce((f, i) -> f instanceof FieldValue ? 736 | ((FieldValue)f).column : new Column(i + ""))) 737 | .collect(toList()); 738 | } 739 | 740 | @Override 741 | Records records() { 742 | Records records = from.records(); 743 | Map columnIndex = from.getColumnIndex(); 744 | //集計関数を集める 745 | List aggs = new ArrayList<>(); 746 | fields.forEach(f -> gatherAggregation(aggs, f)); 747 | aggs.forEach(a -> a.reset()); 748 | //集計行インデックス 749 | Map grpColIndex = zip(group.stream(),Stream.iterate(0, INC)) 750 | .map(p -> p.reduce((v, i) -> Pair.of( 751 | v instanceof FieldValue ? ((FieldValue)v).column : 752 | new Column(i + "") , i))) 753 | .collect(toMap(p -> p.left, p -> p.right)); 754 | //前の行の値(初期値Null) 755 | List oldValue = IntStream.range(0, group.size()) 756 | .mapToObj(i -> new NullValue()).collect(toList()); 757 | 758 | return new Records() { 759 | private boolean top = true; 760 | private boolean end = false; 761 | 762 | private Optional createResult(List gvalues){ 763 | Tuple t = new Tuple(0, 764 | gvalues.stream().map(sv-> unwrap(sv)).collect(toList())); 765 | List> fv = fields.stream() 766 | .map(f -> eval(f, grpColIndex, t)) 767 | .map(f -> unwrap(f)) 768 | .collect(toList()); 769 | return Optional.of(new Tuple(0, fv)); 770 | } 771 | 772 | @Override 773 | public Optional next() { 774 | if(end){ 775 | return Optional.empty(); 776 | } 777 | for(;;){ 778 | Optional oline = records.next(); 779 | if(oline.isPresent()){ 780 | //まだある 781 | Optional result = Optional.empty(); 782 | //group値を得る 783 | List gvalues = group.stream() 784 | .map(sv -> eval(sv, columnIndex, oline.get())) 785 | .collect(toList()); 786 | if(!group.isEmpty() && !top && //groupがあり先頭ではなく違う 787 | zip(gvalues.stream(), oldValue.stream())//oldValueとの比較 788 | .anyMatch(p -> !p.left.equals(p.right))) 789 | { 790 | //かわった 791 | //結果生成 792 | result = createResult(gvalues); 793 | aggs.forEach(a -> a.reset()); 794 | } 795 | top = false; 796 | //oldValueの更新 797 | zip(Stream.iterate(0, i -> i + 1), gvalues.stream()) 798 | .forEach(p -> oldValue.set(p.left, p.right)); 799 | //集計 800 | aggs.forEach(a -> a.accept(columnIndex, oline.get())); 801 | if(result.isPresent()){ 802 | return result; 803 | } 804 | }else{ 805 | //もうおわり 806 | end = true; 807 | if(group.isEmpty() || !top){ 808 | //集計行がないか最初ではない 809 | //残りの結果を返す 810 | return createResult(oldValue); 811 | } 812 | return Optional.empty(); 813 | } 814 | } 815 | } 816 | }; 817 | } 818 | 819 | @Override 820 | public String toString() { 821 | return "agregate[]<-" + from.toString(); 822 | } 823 | } 824 | 825 | public static SqlValue validate(Map env, AST ast){ 826 | return matchRet(ast, 827 | caseOfRet(StringValue.class, s -> s), 828 | caseOfRet(IntValue.class, i -> i), 829 | caseOfRet(ASTWildcard.class, w -> w), 830 | caseOfRet(ASTBinaryOp.class, c -> 831 | new BinaryOp(validate(env, c.left), validate(env, c.right), c.op.trim())), 832 | caseOfRet(ASTTernaryOp.class, b -> 833 | new TernaryOp(validate(env, b.obj),validate(env, b.start), validate(env, b.end), 834 | "between")), 835 | caseOfRet(ASTIdent.class, id ->{ 836 | List column = findField(env, id.ident); 837 | if(column.isEmpty()){ 838 | throw new RuntimeException(id.ident + " is not found"); 839 | }else if(column.size() > 1){ 840 | throw new RuntimeException(id.ident + " is ambiguous"); 841 | }else{ 842 | return new FieldValue(column.get(0)); 843 | } 844 | }), 845 | caseOfRet(ASTFqn.class, f -> 846 | (SqlValue)Optional.ofNullable(env.get(f.table.ident)) 847 | .orElseThrow(() -> new RuntimeException( 848 | "table " + f.table.ident + " not found")) 849 | .columns.stream().filter(c -> c.name.equals(f.field.ident)) 850 | .findFirst().map(c -> new FieldValue(c)) 851 | .orElseThrow(() -> new RuntimeException( 852 | "field " + f.field.ident + " of " + f.table.ident + " not found")) 853 | ), 854 | caseOfRet(ASTFunc.class, f ->{ 855 | List params = f.params.stream().map(p -> validate(env, p)).collect(toList()); 856 | switch(f.name.ident){ 857 | case "length": 858 | return new LengthFunc(params); 859 | case "now": 860 | return new NowFunc(params); 861 | case "count": 862 | return new CountExp(params); 863 | case "sum": 864 | return new SumExp(params); 865 | default: 866 | throw new RuntimeException(f.name.ident + " function not defined."); 867 | } 868 | }), 869 | noMatchThrow(() -> 870 | new RuntimeException(ast.getClass().getName() + " is wrong type")) 871 | ); 872 | } 873 | 874 | public static SelectPlan analize(Context ctx, SqlParser.ASTSelect sql){ 875 | Map env = new HashMap<>(); 876 | 877 | //From解析 878 | SqlParser.ASTFrom from = sql.from; 879 | 880 | Table table = ctx.schema.find(from.table.ident) 881 | .orElseThrow(() -> 882 | new RuntimeException("table " + from.table.ident + " not found")); 883 | env.put(from.table.ident, table); 884 | QueryPlan primary = new UnionPlan( 885 | new TablePlan(ctx.currentTx, table), 886 | new HistoryPlan(ctx.currentTx, table)); 887 | 888 | for(ASTJoin j : from.joins){ 889 | String t = j.table.ident; 890 | Table tb = ctx.schema.find(t).orElseThrow(() -> 891 | new RuntimeException("join table " + t + " not found")); 892 | env.put(t, tb); 893 | SqlValue cond = validate(env, j.logic); 894 | QueryPlan right = new UnionPlan( 895 | new TablePlan(ctx.currentTx, tb), 896 | new HistoryPlan(ctx.currentTx, tb)); 897 | primary = new JoinPlan(primary, right, cond); 898 | } 899 | 900 | // where 解析 901 | if(sql.where.isPresent()){ 902 | Optional cond = sql.where.map(a -> validate(env, a)); 903 | primary = new FilterPlan(primary, cond.get()); 904 | } 905 | //group by 906 | if(!sql.groupby.isEmpty()){ 907 | // selectでgroup byのフィールド以外が使われていないか走査 908 | List group = sql.groupby.stream().map(gb -> validate(env, gb)).collect(toList()); 909 | //集計用order byの挿入 910 | List> order = group.stream().map(g -> Pair.of(g, true)) 911 | .collect(toList()); 912 | primary = new OrderPlan(primary, order); 913 | //集計の挿入 914 | List aggregation = Stream.of( 915 | sql.select.stream().map(ast -> validate(env, ast)), 916 | sql.order.stream().map(ast -> validate(env, ast.exp)), 917 | sql.having.map(c -> validate(env, c)).map(sv -> Stream.of(sv)).orElse(Stream.empty()) 918 | ).flatMap(s -> s).collect(toList()); 919 | primary = new AggregationPlan(primary, aggregation, group); 920 | //having filter 921 | if(sql.having.isPresent()){ 922 | primary = new FilterPlan(primary, new FieldIndex(sql.select.size() + sql.order.size())); 923 | } 924 | //order by 925 | if(!sql.order.isEmpty()){ 926 | primary = new OrderPlan(primary, 927 | zip(sql.order.stream(), Stream.iterate(sql.select.size(), INC)) 928 | .map(p -> p.reduce((o, i) -> Pair.of(new FieldIndex(i), o.asc))).collect(toList())); 929 | } 930 | 931 | //select 932 | List columns = IntStream.range(0, sql.select.size()) 933 | .mapToObj(i -> new FieldIndex(i)).collect(toList()); 934 | return new SelectPlan(primary, columns); 935 | } 936 | 937 | // order by 938 | if(!sql.order.isEmpty()){ 939 | List> order = sql.order.stream() 940 | .map(ov -> Pair.of(validate(env, ov.exp), ov.asc)) 941 | .collect(toList()); 942 | primary = new OrderPlan(primary, order); 943 | } 944 | 945 | //Select解析 946 | //todo 集計関数の確認 947 | List columns = sql.select.stream() 948 | .map(c -> validate(env, c)) 949 | .collect(toList()); 950 | return new SelectPlan(primary, columns); 951 | } 952 | 953 | static boolean hasOr(SqlValue v){ 954 | if(!(v instanceof BinaryOp)){ 955 | return false; 956 | } 957 | BinaryOp bin = (BinaryOp) v; 958 | switch(bin.op){ 959 | case "and": 960 | return hasOr(bin.left) || hasOr(bin.right); 961 | case "or": 962 | return true; 963 | default: 964 | return false; 965 | } 966 | } 967 | 968 | static void andSerialize(List ands, SqlValue v){ 969 | if(!(v instanceof BinaryOp)){ 970 | ands.add(v); 971 | return; 972 | } 973 | BinaryOp bin = (BinaryOp) v; 974 | switch(bin.op){ 975 | case "and": 976 | andSerialize(ands, bin.left); 977 | andSerialize(ands, bin.right); 978 | break; 979 | case "or": 980 | throw new RuntimeException("or can not be optimized"); 981 | default: 982 | ands.add(bin); 983 | } 984 | } 985 | 986 | public static SelectPlan optimize(Schema sc, SelectPlan plan){ 987 | //whereがないなら最適化しない 988 | if(!(plan.from instanceof FilterPlan)){ 989 | return plan; 990 | } 991 | FilterPlan filter = (FilterPlan) plan.from; 992 | List conds = filter.conds; 993 | //orが入ってたら最適化しない 994 | if(conds.stream().anyMatch(cond -> hasOr(cond))){ 995 | return plan; 996 | } 997 | 998 | //プライマリテーブルを取得 999 | QueryPlan q = filter; 1000 | for(;q instanceof NodePlan; q = ((NodePlan)q).from){ 1001 | // do nothing; 1002 | } 1003 | TablePlan t = (TablePlan) q; 1004 | 1005 | //andをリストに分解 1006 | List ands = new ArrayList<>(); 1007 | conds.forEach(cond -> andSerialize(ands, cond)); 1008 | 1009 | boolean alwaysFalse = false; 1010 | List root = new ArrayList<>(); 1011 | for(Iterator ite = ands.iterator(); ite.hasNext();){ 1012 | SqlValue v = ite.next(); 1013 | if(v instanceof BinaryOp){ 1014 | BinaryOp bin = (BinaryOp) v; 1015 | //定数同士での比較はあらかじめ計算する 1016 | if(bin.left instanceof IntValue && bin.right instanceof IntValue){ 1017 | SqlValue b = eval(v, null, null); 1018 | if(b instanceof BooleanValue){ 1019 | if(((BooleanValue)b).value){ 1020 | //常に真 1021 | ite.remove();//計算不要 1022 | continue; 1023 | }else{ 1024 | //常に偽 1025 | alwaysFalse = true; 1026 | break; 1027 | } 1028 | } 1029 | } 1030 | //片方がフィールドのとき 1031 | if((bin.left instanceof FieldValue && !(bin.right instanceof FieldValue)) || 1032 | (!(bin.left instanceof FieldValue) && bin.right instanceof FieldValue)){ 1033 | FieldValue f = (FieldValue)((bin.left instanceof FieldValue) ? bin.left : bin.right); 1034 | f.column.parent 1035 | .filter(pt -> pt.name.equals(t.table.name)) 1036 | .ifPresent(pt -> 1037 | { 1038 | root.add(bin); 1039 | ite.remove(); 1040 | }); 1041 | } 1042 | 1043 | } 1044 | } 1045 | 1046 | if(alwaysFalse){ 1047 | //常に偽 1048 | ands.clear(); 1049 | root.clear(); 1050 | //テーブルの前にemptyフィルター 1051 | Optional oto = t.to; 1052 | EmptyPlan ep = new EmptyPlan(t); 1053 | ep.to = oto; 1054 | oto.ifPresent(to -> to.from = ep); 1055 | } 1056 | 1057 | if(ands.isEmpty()){ 1058 | //もとの条件式が空になったらfilterをはずす 1059 | filter.from.to = filter.to; 1060 | filter.to.ifPresent(to -> to.from = filter.from); 1061 | }else{ 1062 | //空でなければfilterいれかえ 1063 | FilterPlan newFilter = new FilterPlan(filter.from, ands); 1064 | filter.to.ifPresent(to -> to.from = newFilter); 1065 | newFilter.to = filter.to; 1066 | } 1067 | 1068 | if(!root.isEmpty()){ 1069 | //テーブルフィルターが空でなければfilter挿入 1070 | Optional oto = t.to; 1071 | FilterPlan newFilter = new FilterPlan(t, root); 1072 | newFilter.to = oto; 1073 | oto.ifPresent(to -> to.from = newFilter); 1074 | } 1075 | 1076 | return plan; 1077 | } 1078 | 1079 | public static int insert(Context ctx, ASTInsert insert){ 1080 | Table t = ctx.schema.find(insert.table.ident) 1081 | .orElseThrow(() -> new RuntimeException( 1082 | String.format("table %s not found.", insert.table.ident))); 1083 | 1084 | Counter c = new Counter(); 1085 | Map cols = t.columns.stream() 1086 | .collect(toMap(col -> col.name, col -> c.getCount() - 1)); 1087 | int[] indexes; 1088 | if(insert.field.isPresent()){ 1089 | indexes = insert.field.get().stream().mapToInt(id -> cols.get(id.ident)).toArray(); 1090 | }else{ 1091 | indexes = IntStream.range(0, t.columns.size()).toArray(); 1092 | } 1093 | 1094 | ctx.withTx(tx -> { 1095 | insert.value.forEach(ro -> { 1096 | Object[] row = new Object[t.columns.size()]; 1097 | for(int i = 0; i < ro.size(); ++i){ 1098 | row[indexes[i]] = unwrap(validate(null, ro.get(i))).orElse(null); 1099 | } 1100 | t.insert(tx, row); 1101 | }); 1102 | }); 1103 | return insert.value.size(); 1104 | } 1105 | 1106 | public static int delete(Context ctx, ASTDelete del){ 1107 | Table t = ctx.schema.find(del.table.ident) 1108 | .orElseThrow(() -> new RuntimeException( 1109 | String.format("table %s not found.", del.table.ident))); 1110 | Map env = new HashMap<>(); 1111 | env.put(del.table.ident, t); 1112 | QueryPlan primary = new TablePlan(ctx.currentTx, t); 1113 | 1114 | if(del.where.isPresent()){ 1115 | SqlValue cond = del.where.map(a -> validate(env, a)).get(); 1116 | List ands = new ArrayList<>(); 1117 | if(hasOr(cond)){ 1118 | ands.add(cond); 1119 | }else{ 1120 | andSerialize(ands, cond); 1121 | } 1122 | primary = new FilterPlan(primary, ands); 1123 | } 1124 | Records rec = primary.records(); 1125 | List deletes = new ArrayList<>(); 1126 | for(Optional line; (line = rec.next()).isPresent(); ){ 1127 | deletes.add((TableTuple)line.get()); 1128 | } 1129 | ctx.withTx(tx -> t.delete(tx, deletes)); 1130 | return deletes.size(); 1131 | } 1132 | public static int update(Context ctx, ASTUpdate update){ 1133 | Table t = ctx.schema.find(update.table.ident) 1134 | .orElseThrow(() -> new RuntimeException( 1135 | String.format("table %s not found.", update.table.ident))); 1136 | Map env = new HashMap<>(); 1137 | env.put(update.table.ident, t); 1138 | QueryPlan primary = new TablePlan(ctx.currentTx, t); 1139 | 1140 | if(update.where.isPresent()){ 1141 | SqlValue cond = update.where.map(a -> validate(env, a)).get(); 1142 | List ands = new ArrayList<>(); 1143 | if(hasOr(cond)){ 1144 | ands.add(cond); 1145 | }else{ 1146 | andSerialize(ands, cond); 1147 | } 1148 | primary = new FilterPlan(primary, ands); 1149 | } 1150 | Counter c = new Counter(); 1151 | Map cols = t.columns.stream() 1152 | .collect(toMap(col -> col.name, col -> c.getCount() - 1)); 1153 | List> values = 1154 | update.values.stream().map(v -> new AbstractMap.SimpleEntry<>( 1155 | cols.get(v.field.ident), validate(env, v.value))).collect(toList()); 1156 | Map colIdx = primary.getColumnIndex(); 1157 | Records rec = primary.records(); 1158 | int[] ct = {0}; 1159 | ctx.withTx(tx -> { 1160 | for(Optional oline; (oline = rec.next()).isPresent(); ){ 1161 | Tuple line = oline.get(); 1162 | List> copy = new ArrayList<>(line.row); 1163 | while(copy.size() < t.columns.size()){ 1164 | copy.add(Optional.empty()); 1165 | } 1166 | 1167 | values.forEach(me -> { 1168 | copy.set(me.getKey(), unwrap(eval(me.getValue(), colIdx, line))); 1169 | }); 1170 | t.update(tx, line.rid, copy); 1171 | ++ct[0]; 1172 | } 1173 | }); 1174 | return ct[0]; 1175 | } 1176 | 1177 | public static void createTable(Schema sc, ASTCreateTable ct){ 1178 | Table table = new Table(ct.tableName.ident, 1179 | ct.fields.stream().map(id -> new Column(id.ident)).collect(toList())); 1180 | sc.tables.put(table.name, table); 1181 | } 1182 | 1183 | public static void createIndex(Schema sc, ASTCreateIndex ci){ 1184 | String tblname = ci.table.map(t -> t.ident).orElseThrow(() -> 1185 | new RuntimeException("need table for index")); 1186 | Table tbl = sc.find(tblname).orElseThrow(() -> 1187 | new RuntimeException("table not found:" + tblname)); 1188 | if(ci.field.size() != 1) throw new RuntimeException("multiple index field is not supported."); 1189 | Pair colidx = zip(tbl.columns.stream(), Stream.iterate(0, INC)) 1190 | .filter(p -> p.left.name.equals(ci.field.get(0).ident)) 1191 | .findFirst() 1192 | .orElseThrow(() -> new RuntimeException("field is not found:" + ci.field.get(0).ident)); 1193 | 1194 | String method = ci.method.map(m -> m.ident).orElse("tree"); 1195 | Index idx; 1196 | switch(method){ 1197 | case "tree": 1198 | idx = new Index.TreeIndex(colidx.right); 1199 | break; 1200 | case "hash": 1201 | idx = new Index.HashIndex(colidx.right); 1202 | break; 1203 | default: 1204 | throw new RuntimeException("index method " + method + " is not supported"); 1205 | } 1206 | tbl.addIndex(colidx.left, idx); 1207 | } 1208 | 1209 | public static class IntRecords implements Records{ 1210 | int value; 1211 | boolean top; 1212 | 1213 | public IntRecords(int value) { 1214 | this.value = value; 1215 | top = true; 1216 | } 1217 | 1218 | @Override 1219 | public Optional next() { 1220 | if(top){ 1221 | top = false; 1222 | return Optional.of(new Tuple(0, Arrays.asList(Optional.of(value)))); 1223 | }else{ 1224 | return Optional.empty(); 1225 | } 1226 | } 1227 | } 1228 | 1229 | public static Records exec(Context ctx, String sqlstr){ 1230 | Parser parser = SqlParser.parser(); 1231 | SqlParser.AST sql = parser.parse(sqlstr); 1232 | if(sql instanceof ASTInsert){ 1233 | return new IntRecords(insert(ctx, (ASTInsert) sql)); 1234 | }else if(sql instanceof ASTUpdate){ 1235 | return new IntRecords(update(ctx, (ASTUpdate)sql)); 1236 | }else if(sql instanceof ASTDelete){ 1237 | return new IntRecords(delete(ctx, (ASTDelete) sql)); 1238 | }else if(sql instanceof ASTCreateTable){ 1239 | createTable(ctx.schema, (ASTCreateTable)sql); 1240 | return new IntRecords(0); 1241 | }else if(sql instanceof ASTCreateIndex){ 1242 | createIndex(ctx.schema, (ASTCreateIndex)sql); 1243 | return new IntRecords(0); 1244 | }else if(!(sql instanceof ASTSelect)){ 1245 | throw new RuntimeException("not supported"); 1246 | } 1247 | ASTSelect select = (ASTSelect) sql; 1248 | SelectPlan plan = analize(ctx, select); 1249 | System.out.println(sqlstr); 1250 | System.out.println("初期プラン:" + plan); 1251 | plan = optimize(ctx.schema, plan); 1252 | System.out.println("論理最適化:" + plan); 1253 | 1254 | return plan.records(); 1255 | } 1256 | 1257 | static void printResult(Records result){ 1258 | for(Optional line; (line = result.next()).isPresent();){ 1259 | line.ifPresent(l -> { 1260 | System.out.println(l.row.stream() 1261 | .map(o -> o.map(v -> v.toString()).orElse("null")) 1262 | .collect(joining(",", "[", "]"))); 1263 | }); 1264 | } 1265 | System.out.println(); 1266 | } 1267 | 1268 | public static void main(String[] args) { 1269 | Schema sc = new Schema(); 1270 | Context ctx = sc.createContext(); 1271 | Context ctx2 = sc.createContext(); 1272 | Context ctx3 = sc.createContext(); 1273 | ctx.exec("create table shohin(id, name, bunrui_id, price)"); 1274 | ctx.exec("create table bunrui(id, name, seisen)"); 1275 | 1276 | ctx.begin(); 1277 | ctx.exec("insert into bunrui(id, name, seisen) values" + 1278 | "(1, '野菜', 1)," + 1279 | "(2, 'くだもの', 1)," + 1280 | "(3, '菓子', 2)," + 1281 | "(9, '団子', 0)"); 1282 | System.out.println("コミット前"); 1283 | printResult(ctx2.exec("select * from bunrui")); 1284 | ctx3.begin(); 1285 | ctx.commit(); 1286 | System.out.println("コミット後"); 1287 | printResult(ctx2.exec("select * from bunrui")); 1288 | System.out.println("コミット前に始まったトランザクション"); 1289 | printResult(ctx3.exec("select * from bunrui")); 1290 | ctx.exec("insert into shohin(id, name, bunrui_id, price) values" + 1291 | "(1, 'りんご', 2, 250),"+ 1292 | "(2, 'キャベツ', 1, 200),"+ 1293 | "(3, 'たけのこの', 3, 150),"+ 1294 | "(4, 'きのこ', 3, 120),"+ 1295 | "(5, 'パソコン', 0, 34800),"+ 1296 | "(6, 'のこぎり')"); 1297 | 1298 | ctx.exec("create table member(id, name, address)"); 1299 | ctx.exec("insert into member values(1, 'きしだ', '福岡'), (2, 'ほうじょう', '京都')"); 1300 | printResult(ctx.exec("select * from member")); 1301 | 1302 | ctx.exec("insert into bunrui values(4, '周辺機器', 2)"); 1303 | ctx.exec("insert into bunrui(name, id) values('酒', 5 )"); 1304 | ctx.exec("insert into bunrui(id, name) values(6, 'ビール' )"); 1305 | ctx.exec("insert into bunrui(id, name, seisen) values(7, '麺', 2), (8, '茶', 2)"); 1306 | printResult(ctx.exec("select bunrui_id, sum(price), count(price), 2 + 3 from shohin group by bunrui_id having count(price)>0 order by sum(price)")); 1307 | printResult(ctx.exec("select id,name, now(), length(name) from bunrui")); 1308 | printResult(ctx.exec("select * from bunrui")); 1309 | printResult(ctx.exec("select * from bunrui order by id desc")); 1310 | printResult(ctx.exec("select * from bunrui order by seisen, id desc")); 1311 | 1312 | printResult(ctx.exec("select id,name,price,price*2 from shohin")); 1313 | printResult(ctx.exec("select id, name from shohin where price between 130 and 200 or id=1")); 1314 | printResult(ctx.exec("select id, name from shohin where price between 130 and 200")); 1315 | System.out.println("普通のJOIN"); 1316 | printResult(ctx.exec("select shohin.id, shohin.name,bunrui.name" 1317 | + " from shohin left join bunrui on shohin.bunrui_id=bunrui.id")); 1318 | System.out.println("常に真なので条件省略"); 1319 | printResult(ctx.exec("select id, name from shohin where 2 < 3")); 1320 | System.out.println("常に偽なので空になる"); 1321 | printResult(ctx.exec("select id, name from shohin where price < 130 and 2 > 3")); 1322 | System.out.println("メインテーブルのみに関係のある条件はJOINの前に適用"); 1323 | printResult(ctx.exec("select shohin.id, shohin.name,bunrui.name" 1324 | + " from shohin left join bunrui on shohin.bunrui_id=bunrui.id" 1325 | + " where shohin.price <= 300 and bunrui.seisen=1")); 1326 | System.out.println("update"); 1327 | ctx.exec("update shohin set price=1500 where id=6"); 1328 | ctx.exec("update shohin set price=price+500 where id=5"); 1329 | printResult(ctx.exec("select * from shohin where id=6 or id=5")); 1330 | ctx.exec("update shohin set price=price*105/100"); 1331 | printResult(ctx.exec("select * from shohin")); 1332 | System.out.println("削除"); 1333 | ctx.exec("delete from bunrui where id=9"); 1334 | printResult(ctx.exec("select * from bunrui")); 1335 | System.out.println("全削除"); 1336 | ctx.exec("delete from bunrui"); 1337 | printResult(ctx.exec("select * from bunrui")); 1338 | } 1339 | } 1340 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/SqlParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import kis.sqlparser.SqlAnalizer.SqlValue; 14 | import lombok.AllArgsConstructor; 15 | import lombok.EqualsAndHashCode; 16 | import lombok.ToString; 17 | import org.codehaus.jparsec.OperatorTable; 18 | import org.codehaus.jparsec.Parser; 19 | import org.codehaus.jparsec.Parsers; 20 | import org.codehaus.jparsec.Scanners; 21 | import org.codehaus.jparsec.Terminals; 22 | 23 | /** 24 | * 25 | * @author naoki 26 | */ 27 | public class SqlParser { 28 | static final String[] keywords = { 29 | "between", "and", "or", "select", "from", "left", "join", "on", "where", 30 | "insert", "into", "values", "update", "set", "delete", 31 | "order", "by", "asc", "desc", "group", "having", 32 | "create", "table", "index", "using" 33 | }; 34 | 35 | static final String[] operators = { 36 | "=", "<", ">", "<=", ">=", ".", "*", ",", "(", ")", "+", "-", "/" 37 | }; 38 | 39 | static final Terminals terms = Terminals.caseInsensitive(operators, keywords); 40 | static Parser ignored = Scanners.WHITESPACES.optional(); 41 | static Parser tokenizer = Parsers.or( 42 | terms.tokenizer(), 43 | Terminals.Identifier.TOKENIZER, 44 | Terminals.StringLiteral.SINGLE_QUOTE_TOKENIZER, 45 | Terminals.IntegerLiteral.TOKENIZER); 46 | 47 | public static interface AST{} 48 | public static interface ASTExp extends AST{} 49 | // integer 50 | @AllArgsConstructor @EqualsAndHashCode 51 | public static class IntValue implements ASTExp, SqlValue{ 52 | int value; 53 | @Override 54 | public String toString() { 55 | return value + ""; 56 | } 57 | } 58 | public static Parser integer(){ 59 | return Terminals.IntegerLiteral.PARSER.map(s -> new IntValue(Integer.parseInt(s))); 60 | } 61 | 62 | // identifier 63 | @AllArgsConstructor @ToString 64 | public static class ASTIdent implements ASTExp{ 65 | String ident; 66 | } 67 | public static Parser identifier(){ 68 | return Terminals.Identifier.PARSER.map(s -> new ASTIdent(s)); 69 | } 70 | 71 | // str 72 | @AllArgsConstructor @EqualsAndHashCode 73 | public static class StringValue implements ASTExp, SqlValue{ 74 | String value; 75 | @Override 76 | public String toString() { 77 | return "'" + value + "'"; 78 | } 79 | 80 | } 81 | public static Parser str(){ 82 | return Terminals.StringLiteral.PARSER.map(s -> new StringValue( 83 | s.replaceAll("''", "'"))); 84 | } 85 | 86 | // fqn := identifier "." identifier 87 | @AllArgsConstructor @ToString 88 | public static class ASTFqn implements ASTExp{ 89 | ASTIdent table; 90 | ASTIdent field; 91 | } 92 | public static Parser fqn(){ 93 | return identifier().next(t -> terms.token(".").next(identifier()).map(f -> new ASTFqn(t, f))); 94 | } 95 | 96 | @AllArgsConstructor @ToString 97 | public static class ASTFunc implements ASTExp{ 98 | ASTIdent name; 99 | List params; 100 | } 101 | 102 | public static Parser func(){ 103 | Parser func = identifier().next(id -> 104 | value().sepBy(terms.token(",")).between(terms.token("("), terms.token(")")) 105 | .map(params -> new ASTFunc(id, params))); 106 | return func; 107 | } 108 | 109 | // value := fqn | identifier | integer | str 110 | public static Parser value(){ 111 | return Parsers.or(fqn(), func(), identifier(), integer(), str()); 112 | } 113 | 114 | public static Parser expression(){ 115 | return new OperatorTable() 116 | .infixl(terms.token("+").retn((l, r) -> new ASTBinaryOp(l, r, "+")), 10) 117 | .infixl(terms.token("-").retn((l, r) -> new ASTBinaryOp(l, r, "-")), 10) 118 | .infixl(terms.token("/").retn((l, r) -> new ASTBinaryOp(l, r, "/")), 20) 119 | .infixl(terms.token("*").retn((l, r) -> new ASTBinaryOp(l, r, "*")), 20) 120 | .build(value()); 121 | } 122 | 123 | // bicond := value ("=" | "<" | "<=" | ">" | ">=) value 124 | @AllArgsConstructor @ToString 125 | public static class ASTBinaryOp implements ASTExp{ 126 | ASTExp left; 127 | ASTExp right; 128 | String op; 129 | } 130 | 131 | public static Parser bicond(){ 132 | return expression().next(l -> 133 | terms.token("=", "<", "<=", ">", ">=").source() 134 | .next(op -> 135 | expression().map(r -> new ASTBinaryOp(l, r, op)))); 136 | } 137 | 138 | // between := value "between" value "and" value 139 | @AllArgsConstructor @ToString 140 | public static class ASTTernaryOp implements ASTExp{ 141 | ASTExp obj; 142 | ASTExp start; 143 | ASTExp end; 144 | String op; 145 | } 146 | 147 | public static Parser between(){ 148 | return expression().next(o -> 149 | terms.token("between").next(expression()).next(st -> 150 | terms.token("and").next(expression()).map(ed -> 151 | new ASTTernaryOp(o, st, ed, "between")))); 152 | } 153 | // cond := bicond | between 154 | public static Parser cond(){ 155 | return Parsers.or(bicond(), between()); 156 | } 157 | 158 | public static Parser logic(){ 159 | return new OperatorTable() 160 | .infixl(terms.token("and").retn((l, r) -> new ASTBinaryOp(l, r, "and")), 1) 161 | .infixl(terms.token("or").retn((l, r) -> new ASTBinaryOp(l, r, "or")), 1) 162 | .build(cond()); 163 | } 164 | 165 | public static interface ASTStatement extends AST{} 166 | // select := "select" value ("," value)* 167 | public static class ASTWildcard implements AST, SqlValue{ 168 | @Override 169 | public String toString() { 170 | return "*"; 171 | } 172 | } 173 | 174 | public static Parser> select(){ 175 | return terms.token("select").next(Parsers.or( 176 | terms.token("*").map(t -> Arrays.asList(new ASTWildcard())), 177 | expression().sepBy1(terms.token(",")))); 178 | } 179 | 180 | // table := identifier 181 | // field := identifier | fqn 182 | // join := "left" "join" table "on" logic 183 | @AllArgsConstructor @ToString 184 | public static class ASTJoin implements AST{ 185 | ASTIdent table; 186 | AST logic; 187 | } 188 | 189 | public static Parser join(){ 190 | return terms.phrase("left", "join") 191 | .next(identifier().next(t -> terms.token("on") 192 | .next(logic()).map(lg -> new ASTJoin(t, lg)))); 193 | } 194 | 195 | // from := "from" table join* 196 | @AllArgsConstructor @ToString 197 | public static class ASTFrom implements AST{ 198 | ASTIdent table; 199 | List joins; 200 | } 201 | 202 | public static Parser from(){ 203 | return terms.token("from").next(identifier() 204 | .next(t -> join().many().map(j -> new ASTFrom(t, j)))); 205 | } 206 | // where := "where" logic 207 | public static Parser where(){ 208 | return terms.token("where").next(logic()); 209 | } 210 | 211 | // ordervalue := expression ("asc"|"desc")? 212 | @AllArgsConstructor @ToString 213 | public static class ASTOrderValue implements AST{ 214 | ASTExp exp; 215 | boolean asc; 216 | } 217 | public static Parser orderValue(){ 218 | return expression().next(x -> Parsers.or( 219 | terms.token("asc").retn(true), 220 | terms.token("desc").retn(false)).optional(true) 221 | .map(b -> new ASTOrderValue(x, b))); 222 | } 223 | // orderby := "order" "by" ordervalue ("," ordervalue)* 224 | public static Parser> orderby(){ 225 | return terms.phrase("order", "by") 226 | .next(orderValue().sepBy(terms.token(","))); 227 | } 228 | 229 | // group by := "group" "by" ident ("," ident)* 230 | public static Parser> groupby(){ 231 | return terms.phrase("group", "by") 232 | .next(Parsers.or(fqn(), identifier())).sepBy1(terms.token(",")); 233 | } 234 | 235 | // having := "having" logic 236 | public static Parser having(){ 237 | return terms.token("having").next(logic()); 238 | } 239 | 240 | // selectStatement := select from where? 241 | @AllArgsConstructor @ToString 242 | public static class ASTSelect implements ASTStatement{ 243 | List select; 244 | ASTFrom from; 245 | Optional where; 246 | List groupby; 247 | Optional having; 248 | List order; 249 | } 250 | public static Parser selectStatement(){ 251 | return Parsers.sequence( 252 | select(), from(), where().optional(), 253 | groupby().next(g -> having().optional().map(h -> Pair.of(g, h))).optional(), 254 | orderby().optional(), 255 | (s, f, w, p, o) -> 256 | new ASTSelect(s, f, Optional.ofNullable(w), 257 | p == null ? Collections.EMPTY_LIST : p.left, 258 | p == null ? Optional.empty() : Optional.ofNullable(p.right), 259 | o == null ? Collections.EMPTY_LIST : o)); 260 | } 261 | 262 | // insertField := "(" identity ("," identity)* ")" 263 | public static Parser> insertField(){ 264 | return Parsers.between( 265 | terms.token("("), 266 | identifier().sepBy1(terms.token(",")) , 267 | terms.token(")")); 268 | } 269 | 270 | public static Parser> insertValues(){ 271 | return Parsers.between( 272 | terms.token("("), 273 | Parsers.or(integer(), str()).sepBy1(terms.token(",")), 274 | terms.token(")")); 275 | } 276 | 277 | // insert := "insert" "into" table insertField? "values" insertValues ("," insertValues)* 278 | @AllArgsConstructor @ToString 279 | public static class ASTInsert implements ASTStatement{ 280 | ASTIdent table; 281 | Optional> field; 282 | List> value; 283 | } 284 | 285 | public static Parser insert(){ 286 | return Parsers.sequence( 287 | terms.phrase("insert", "into").next(identifier()), 288 | insertField().optional(), 289 | terms.token("values").next(insertValues().sepBy1(terms.token(","))), 290 | (tb, f, v) -> new ASTInsert(tb, Optional.ofNullable(f), v)); 291 | } 292 | 293 | @AllArgsConstructor 294 | public static class ASTDelete implements ASTStatement{ 295 | ASTIdent table; 296 | Optional where; 297 | } 298 | 299 | public static Parser delete(){ 300 | return Parsers.sequence( 301 | terms.token("delete").next(terms.token("*").optional()) 302 | .next(terms.token("from")).next(identifier()), 303 | where().optional(), 304 | (id, w) -> new ASTDelete(id, Optional.ofNullable(w))); 305 | } 306 | 307 | // insertValue := ident "=" value 308 | @AllArgsConstructor @ToString 309 | public static class ASTUpdateValue implements AST{ 310 | ASTIdent field; 311 | AST value; 312 | } 313 | 314 | public static Parser updateValue(){ 315 | return Parsers.sequence( 316 | identifier(), 317 | terms.token("=").next(expression()), 318 | (id, v) -> new ASTUpdateValue(id, v)); 319 | } 320 | 321 | // update := "update" table "set" updateValue ("," updateValue)* where 322 | @AllArgsConstructor @ToString 323 | public static class ASTUpdate implements ASTStatement{ 324 | ASTIdent table; 325 | List values; 326 | Optional where; 327 | } 328 | 329 | public static Parser update(){ 330 | return Parsers.sequence( 331 | terms.token("update").next(identifier()), 332 | terms.token("set").next(updateValue().sepBy1(terms.token(","))), 333 | where().optional(), 334 | (tbl, values, where) -> new ASTUpdate(tbl, values, Optional.ofNullable(where))); 335 | } 336 | 337 | // createindex := "create" "index" ident? "on" ident ("using" ident)? "(" ident ("," ident)* ")" 338 | @AllArgsConstructor @ToString 339 | public static class ASTCreateIndex implements ASTStatement{ 340 | Optional indexName; 341 | Optional table; 342 | Optional method; 343 | List field; 344 | } 345 | public static Parser createIndex(){ 346 | return Parsers.sequence( 347 | terms.phrase("create", "index").next(identifier().optional()), 348 | terms.token("on").next(identifier()), 349 | terms.token("using").next(identifier()).optional(), 350 | identifier().sepBy(terms.token(",")).between(terms.token("("), terms.token(")")), 351 | (n, t, m, f) -> new ASTCreateIndex( 352 | Optional.ofNullable(n), Optional.of(t), Optional.ofNullable(m), f)); 353 | } 354 | // createtableField := ident (ident ("(" integer ")")?)? 355 | 356 | 357 | // createtableIndex := "index" ident? ("using" ident)? "(" ident ("," ident)* ")" 358 | public static Parser createTableIndex(){ 359 | return Parsers.sequence( 360 | terms.token("index").next(identifier().optional()), 361 | terms.token("using").next(identifier()).optional(), 362 | identifier().sepBy(terms.token(",")).between(terms.token("("), terms.token(")")), 363 | (n, m, f) -> new ASTCreateIndex( 364 | Optional.ofNullable(n), Optional.empty(), Optional.ofNullable(m), f)); 365 | } 366 | // createtable := "create" "table" ident "(" ident ("," ident)* ")" 367 | @AllArgsConstructor @ToString 368 | public static class ASTCreateTable implements ASTStatement{ 369 | ASTIdent tableName; 370 | List fields; 371 | } 372 | public static Parser createTable(){ 373 | return Parsers.sequence( 374 | terms.phrase("create", "table").next(identifier()), 375 | identifier().sepBy1(terms.token(",")).between(terms.token("("), terms.token(")")), 376 | (t, f) -> new ASTCreateTable(t, f)); 377 | } 378 | 379 | public static Parser parser(){ 380 | return Parsers.or(selectStatement(), insert(), update(), delete(), createTable(), createIndex()).from(tokenizer, ignored); 381 | } 382 | 383 | } 384 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/StreamUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.Iterator; 10 | import java.util.Spliterator; 11 | import java.util.Spliterators; 12 | import java.util.function.BiConsumer; 13 | import java.util.stream.Stream; 14 | import java.util.stream.StreamSupport; 15 | 16 | /** 17 | * 18 | * @author naoki 19 | */ 20 | public class StreamUtils { 21 | public static void zip(Stream f, Stream s, BiConsumer bc){ 22 | Iterator fite = f.iterator(); 23 | Iterator site = s.iterator(); 24 | while(fite.hasNext() && site.hasNext()){ 25 | bc.accept(fite.next(), site.next()); 26 | } 27 | } 28 | public static Stream> zip(Stream f, Stream s){ 29 | Iterator fite = f.iterator(); 30 | Iterator site = s.iterator(); 31 | Iterator> iterator = new Iterator>(){ 32 | @Override 33 | public boolean hasNext() { 34 | return fite.hasNext() && site.hasNext(); 35 | } 36 | @Override 37 | public Pair next() { 38 | return Pair.of(fite.next(), site.next()); 39 | } 40 | }; 41 | 42 | return StreamSupport.stream( 43 | Spliterators.spliteratorUnknownSize( 44 | iterator, Spliterator.NONNULL | Spliterator.ORDERED), false); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Table.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.LinkedHashMap; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Optional; 17 | import java.util.stream.Collectors; 18 | import lombok.AllArgsConstructor; 19 | import lombok.EqualsAndHashCode; 20 | 21 | /** 22 | * 23 | * @author naoki 24 | */ 25 | public class Table { 26 | @AllArgsConstructor 27 | @EqualsAndHashCode(of = "rid") 28 | public static class Tuple{ 29 | long rid; 30 | List> row; 31 | } 32 | 33 | public static class TableTuple extends Tuple{ 34 | Table table; 35 | long createTx; 36 | long commitTx; 37 | boolean modified; 38 | 39 | public TableTuple(Table table, long rid, Transaction tx, List> row) { 40 | this(table, rid, tx.txId, row); 41 | } 42 | public TableTuple(Table table, long rid, long txid, List> row) { 43 | super(rid, row); 44 | this.table = table; 45 | this.createTx = txid; 46 | commitTx = 0; 47 | modified = false; 48 | } 49 | public TableTuple(TableTuple tt){ 50 | this(tt.table, tt.rid, tt.createTx, tt.row); 51 | commitTx = tt.commitTx; 52 | modified = tt.modified; 53 | } 54 | public void commit(long txId){ 55 | commitTx = txId; 56 | } 57 | public boolean isCommited(){ 58 | return commitTx != 0; 59 | } 60 | } 61 | String name; 62 | List columns; 63 | static long rid; 64 | Map> indexes; 65 | 66 | LinkedHashMap data; 67 | HashMap> modifiedTuples; 68 | 69 | public Table(String name, List columns){ 70 | this.name = name; 71 | this.columns = columns.stream() 72 | .map(col -> new Column(this, col.name)) 73 | .collect(Collectors.toList()); 74 | this.data = new LinkedHashMap<>(); 75 | this.modifiedTuples = new HashMap<>(); 76 | this.indexes = new HashMap<>(); 77 | } 78 | 79 | public Table insert(Transaction tx, Object... values){ 80 | if(columns.size() < values.length){ 81 | throw new RuntimeException("values count is over the number of columns"); 82 | } 83 | ++rid; 84 | TableTuple tuple = new TableTuple(this, rid, tx, 85 | Arrays.stream(values) 86 | .map(Optional::ofNullable) 87 | .collect(Collectors.toList())); 88 | tx.insertTuples.add(tuple); 89 | dataInsert(tuple); 90 | return this; 91 | } 92 | public void dataInsert(TableTuple tuple){ 93 | data.put(tuple.rid, tuple); 94 | indexes.values().stream().flatMap(is -> is.stream()).forEach(idx -> idx.insert(tuple)); 95 | } 96 | 97 | void update(Transaction tx, long rid, List> copy) { 98 | //自分のトランザクションで削除したものは変更できないし、よそのトランザクションで削除・変更されたものも変更できない 99 | TableTuple oldtuple = data.get(rid); 100 | if(oldtuple == null || oldtuple.modified){ 101 | throw new RuntimeException("modify conflict"); 102 | } 103 | //元の値を保存 104 | TableTuple tuple = new TableTuple(oldtuple); 105 | tuple.commitTx = 0;//未コミット 106 | tuple.createTx = tx.txId; 107 | //変更反映 108 | tuple.row = copy; 109 | dataUpdate(tuple, oldtuple); 110 | if(oldtuple.createTx == tx.txId){ 111 | //自分のトランザクションで変更したデータは履歴をとらない 112 | return; 113 | } 114 | oldtuple.modified = true; 115 | //履歴を保存 116 | ModifiedTuple.Updated ud = new ModifiedTuple.Updated(oldtuple, tuple, tx.txId); 117 | addModifiedTuple(ud); 118 | tx.modifiedTuples.add(ud); 119 | } 120 | public void dataUpdate(TableTuple tuple, TableTuple oldtuple){ 121 | data.put(tuple.rid, tuple); 122 | indexes.values().stream().flatMap(is -> is.stream()).forEach(idx -> idx.update(oldtuple.row, tuple)); 123 | } 124 | 125 | void delete(Transaction tx, List row) { 126 | if(row.stream().anyMatch(t -> t.modified)){ 127 | throw new RuntimeException("modify conflict"); 128 | } 129 | row.stream().map(t -> t.rid).forEach(data::remove); 130 | indexes.values().stream().flatMap(is -> is.stream()).forEach(idx -> row.forEach(r -> idx.delete(r))); 131 | //履歴を保存 132 | row.stream().filter(t -> t.createTx != tx.txId).forEach(t -> { 133 | t.modified = true; 134 | ModifiedTuple.Deleted dt = new ModifiedTuple.Deleted(t, rid); 135 | addModifiedTuple(dt); 136 | tx.modifiedTuples.add(dt); 137 | }); 138 | 139 | } 140 | void addIndex(Column left, Index idx) { 141 | indexes.computeIfAbsent(left, c -> new ArrayList<>()).add(idx); 142 | } 143 | 144 | List getModifiedTuples(Optional otx){ 145 | if(!otx.isPresent()){ 146 | return modifiedTuples.values().stream() 147 | .map(list -> list.stream() 148 | .filter(mt -> (mt.isCommited() && mt instanceof ModifiedTuple.Updated) ||//コミットされてる更新 149 | (!mt.isCommited() && mt instanceof ModifiedTuple.Deleted)) //もしくはコミットされてない削除 150 | .map(mt -> mt instanceof ModifiedTuple.Updated ? ((ModifiedTuple.Updated)mt).newTuple : mt.oldtuple) 151 | .findFirst()) 152 | .filter(omt -> omt.isPresent()).map(omt -> omt.get()) 153 | .collect(Collectors.toList()); 154 | } 155 | Transaction tx = otx.get(); 156 | return modifiedTuples.values().stream() 157 | .map(list -> list.stream() 158 | .filter(mt -> mt.modiryTx != tx.txId)//自分のトランザクションで変更されたものは省く 159 | .filter(mt -> !mt.isCommited() || (mt.commitTx >= tx.txId)) 160 | .map(mt -> mt.oldtuple) 161 | .filter(ot -> tx.tupleAvailable(ot)).findFirst()) 162 | .filter(omt -> omt.isPresent()).map(omt -> omt.get()) 163 | .collect(Collectors.toList()); 164 | } 165 | 166 | void addModifiedTuple(ModifiedTuple mt){ 167 | modifiedTuples.computeIfAbsent(mt.oldtuple.rid, rid -> new LinkedList()) 168 | .push(mt); 169 | } 170 | void removeModifiedTuple(ModifiedTuple mt){ 171 | modifiedTuples.computeIfPresent(mt.oldtuple.rid, (rid, list) -> { 172 | list.remove(mt); 173 | return list.isEmpty() ? null : list; 174 | }); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/kis/sqlparser/Transaction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import kis.sqlparser.Table.TableTuple; 12 | import lombok.EqualsAndHashCode; 13 | 14 | /** 15 | * 16 | * @author naoki 17 | */ 18 | @EqualsAndHashCode(of = {"schema", "txId"}) 19 | public class Transaction { 20 | Schema schema; 21 | long txId; 22 | boolean enable; 23 | List insertTuples; 24 | List modifiedTuples; 25 | 26 | public Transaction(Schema schema, long txId) { 27 | this.schema = schema; 28 | this.txId = txId; 29 | enable = true; 30 | insertTuples = new ArrayList<>(); 31 | modifiedTuples = new ArrayList<>(); 32 | } 33 | 34 | public void commit(){ 35 | end(); 36 | insertTuples.forEach(t -> t.commit(schema.txId)); 37 | 38 | schema.removeFinTx(); 39 | } 40 | 41 | public void abort(){ 42 | end(); 43 | modifiedTuples.forEach(mt -> mt.abort()); 44 | schema.removeTx(this); 45 | } 46 | 47 | private void end(){ 48 | if(!enable){ 49 | throw new RuntimeException("transaction is not enabled"); 50 | } 51 | enable = false; 52 | } 53 | 54 | public void removeModified(){ 55 | modifiedTuples.forEach(mt -> mt.oldtuple.table.removeModifiedTuple(mt)); 56 | } 57 | 58 | public boolean tupleAvailable(TableTuple tuple){ 59 | if(tuple.createTx != txId){ 60 | //他のトランザクションのデータ 61 | if(tuple.isCommited()){ 62 | //あとのトランザクションでコミットしたものは飛ばす 63 | if(tuple.commitTx >= txId) return false; 64 | }else{ 65 | //コミットされていないものは飛ばす 66 | return false; 67 | } 68 | } 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/kis/sqlparser/AppTest.java: -------------------------------------------------------------------------------- 1 | package kis.sqlparser; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/kis/sqlparser/IndexTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import static java.util.stream.Collectors.*; 14 | import java.util.stream.IntStream; 15 | import java.util.stream.Stream; 16 | import kis.sqlparser.Table.Tuple; 17 | import org.junit.Test; 18 | 19 | /** 20 | * 21 | * @author naoki 22 | */ 23 | public class IndexTest { 24 | List ts; 25 | public IndexTest() { 26 | Object[][] dt = { 27 | {1, "test1"}, 28 | {2, "test2"}, 29 | {3, "test3"}, 30 | {2, "test4"}, 31 | {5, "test5"}, 32 | {4, "test6"}, 33 | }; 34 | ts = new ArrayList<>(); 35 | StreamUtils.zip(Arrays.stream(dt), IntStream.iterate(0, i -> i + 1).boxed(), (d, i) -> { 36 | ts.add(new Table.Tuple(i, Stream.of(d[0], d[1]).map(Optional::of).collect(toList()))); 37 | }); 38 | } 39 | 40 | @Test 41 | public void TreeIndexのinsert() { 42 | Index idx = new Index.TreeIndex(0); 43 | ts.forEach(t -> idx.insert(t)); 44 | print(idx); 45 | } 46 | @Test 47 | public void HashIndexのinsert() { 48 | Index idx = new Index.HashIndex(0); 49 | ts.forEach(t -> idx.insert(t)); 50 | print(idx); 51 | } 52 | 53 | @Test 54 | public void 該当値が全部きえるdelete(){ 55 | Index idx = new Index.HashIndex(0); 56 | ts.forEach(t -> idx.insert(t)); 57 | idx.delete(ts.get(2)); 58 | print(idx); 59 | System.out.println(idx.tuples.size()); 60 | } 61 | @Test 62 | public void 該当値が一部きえるdelete(){ 63 | Index idx = new Index.HashIndex(0); 64 | ts.forEach(t -> idx.insert(t)); 65 | idx.delete(ts.get(3)); 66 | print(idx); 67 | System.out.println(idx.tuples.size()); 68 | } 69 | @Test 70 | public void キーが変更されるupdate(){ 71 | Index idx = new Index.HashIndex(0); 72 | ts.forEach(t -> idx.insert(t)); 73 | List> old = new ArrayList<>(ts.get(0).row); 74 | ts.get(0).row.set(0, Optional.of(5)); 75 | idx.update(old, ts.get(0)); 76 | print(idx); 77 | System.out.println(idx.tuples.size()); 78 | } 79 | @Test 80 | public void キーが変更されないupdate(){ 81 | Index idx = new Index.HashIndex(0); 82 | ts.forEach(t -> idx.insert(t)); 83 | List> old = new ArrayList<>(ts.get(0).row); 84 | ts.get(0).row.set(1, Optional.of("TEST1")); 85 | idx.update(old, ts.get(0)); 86 | print(idx); 87 | } 88 | @Test 89 | public void 範囲(){ 90 | Index.TreeIndex idx = new Index.TreeIndex(0); 91 | ts.forEach(t -> idx.insert(t)); 92 | idx.between(2, 4).forEachRemaining(this::print); 93 | } 94 | @Test 95 | public void 大なりイコール(){ 96 | Index.TreeIndex idx = new Index.TreeIndex(0); 97 | ts.forEach(t -> idx.insert(t)); 98 | idx.compare(2, ">=").forEachRemaining(this::print); 99 | } 100 | @Test 101 | public void 大なり(){ 102 | Index.TreeIndex idx = new Index.TreeIndex(0); 103 | ts.forEach(t -> idx.insert(t)); 104 | idx.compare(2, ">").forEachRemaining(this::print); 105 | } 106 | @Test 107 | public void 小なり(){ 108 | Index.TreeIndex idx = new Index.TreeIndex(0); 109 | ts.forEach(t -> idx.insert(t)); 110 | idx.compare(4, "<").forEachRemaining(this::print); 111 | } 112 | @Test 113 | public void 小なりイコール(){ 114 | Index.TreeIndex idx = new Index.TreeIndex(0); 115 | ts.forEach(t -> idx.insert(t)); 116 | idx.compare(4, "<=").forEachRemaining(this::print); 117 | } 118 | 119 | void print(Index idx){ 120 | idx.tuples.forEach((k, v) -> { 121 | v.forEach(this::print); 122 | }); 123 | 124 | } 125 | void print(Tuple r){ 126 | System.out.printf("%d %s%n", r.rid, 127 | r.row.stream().map(o -> o.map(s -> s.toString()).orElse("null")).collect(joining(","))); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/kis/sqlparser/SqlAnalizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.IntStream; 15 | import java.util.stream.Stream; 16 | import kis.sqlparser.SqlAnalizer.*; 17 | import kis.sqlparser.SqlParser.IntValue; 18 | import kis.sqlparser.Table.Tuple; 19 | import static org.hamcrest.CoreMatchers.is; 20 | import static org.junit.Assert.assertThat; 21 | import org.junit.Test; 22 | 23 | /** 24 | * 25 | * @author naoki 26 | */ 27 | public class SqlAnalizerTest { 28 | 29 | public SqlAnalizerTest() { 30 | } 31 | 32 | @Test 33 | public void testSomeMethod() { 34 | Map cols = new HashMap<>(); 35 | cols.put(new Column("test"), 0); 36 | cols.put(new Column("id"), 1); 37 | cols.put(new Column("name"), 2); 38 | Tuple collect = new Tuple(1, Stream.of(true, 123, "ほげ") 39 | .map(Optional::of) 40 | .collect(Collectors.toList())); 41 | System.out.println(SqlAnalizer.eval(new IntValue(3), cols, collect)); 42 | System.out.println(SqlAnalizer.eval(new BinaryOp(new IntValue(3), new IntValue(3), "="), cols, collect)); 43 | System.out.println(SqlAnalizer.eval(new BinaryOp(new IntValue(2), new IntValue(3), "="), cols, collect)); 44 | System.out.println(SqlAnalizer.eval(new BinaryOp(new IntValue(2), new IntValue(3), "<"), cols, collect)); 45 | System.out.println(SqlAnalizer.eval(new BinaryOp(new IntValue(2), new IntValue(3), ">"), cols, collect)); 46 | System.out.println(SqlAnalizer.eval(new BinaryOp(new BinaryOp(new IntValue(2), new IntValue(3), ">"), new BinaryOp(new IntValue(2), new IntValue(3), "<"),"or"), cols, collect)); 47 | System.out.println(SqlAnalizer.eval(new BinaryOp(new BinaryOp(new IntValue(2), new IntValue(3), ">"), new BinaryOp(new IntValue(2), new IntValue(3), "<"),"and"), cols, collect)); 48 | System.out.println(SqlAnalizer.eval(new FieldValue(new Column("id")), cols, collect)); 49 | System.out.println(SqlAnalizer.eval(new BinaryOp(new FieldValue(new Column("id")), new IntValue(123), "="), cols, collect)); 50 | } 51 | 52 | @Test 53 | public void nullの比較(){ 54 | assertThat(new NullValue().equals(new NullValue()), is(true)); 55 | } 56 | 57 | @Test 58 | public void streamToListはimmutableか(){ 59 | //immutableではないようだ。 60 | List list = IntStream.range(0, 10).boxed().collect(Collectors.toList()); 61 | list.set(3, 0); 62 | list.forEach(i -> System.out.println(i)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/kis/sqlparser/SqlParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.List; 10 | import org.codehaus.jparsec.Parser; 11 | import static org.hamcrest.CoreMatchers.*; 12 | import static org.junit.Assert.assertThat; 13 | import org.junit.Test; 14 | 15 | /** 16 | * 17 | * @author naoki 18 | */ 19 | public class SqlParserTest { 20 | @Test 21 | public void 整数(){ 22 | System.out.println(SqlParser.integer().parse("123")); 23 | } 24 | 25 | @Test 26 | public void 識別子(){ 27 | System.out.println(SqlParser.identifier().parse("shohin")); 28 | } 29 | 30 | @Test 31 | public void 文字列(){ 32 | Parser parser = SqlParser.str().from(SqlParser.tokenizer, SqlParser.ignored); 33 | assertThat(parser.parse("'test'").value, is("test")); 34 | assertThat(parser.parse("''").value , is("")); 35 | assertThat(parser.parse("'tes''t'").value, is("tes't")); 36 | assertThat(parser.parse("'tes''t'''").value, is("tes't'")); 37 | } 38 | 39 | @Test 40 | public void 完全名(){ 41 | System.out.println(SqlParser.fqn().from(SqlParser.tokenizer, SqlParser.ignored).parse("shohin.price")); 42 | } 43 | 44 | @Test 45 | public void 関数(){ 46 | Parser parser = SqlParser.value().from(SqlParser.tokenizer, SqlParser.ignored); 47 | System.out.println(parser.parse("count(floor(price))")); 48 | System.out.println(parser.parse("count(floor(shohin.price))")); 49 | System.out.println(parser.parse("len(a, 3)")); 50 | System.out.println(parser.parse("now()")); 51 | } 52 | 53 | @Test 54 | public void 比較(){ 55 | Parser parser = SqlParser.bicond().from(SqlParser.tokenizer, SqlParser.ignored); 56 | System.out.println(parser.parse("shohin.bunrui_id=bunrui.id")); 57 | System.out.println(parser.parse("shohin . bunrui_id = bunrui . id")); 58 | System.out.println(parser.parse("shohin.bunrui_id <= 12")); 59 | } 60 | 61 | @Test 62 | public void betweentest(){ 63 | System.out.println(SqlParser.between().from(SqlParser.tokenizer, SqlParser.ignored).parse("shohin.price between 100 and 200")); 64 | } 65 | 66 | @Test 67 | public void 論理(){ 68 | System.out.println(SqlParser.logic().from(SqlParser.tokenizer, SqlParser.ignored).parse("bunrui.id=3 and shohin.price between 100 and 200 or bunrui.id=4")); 69 | } 70 | 71 | @Test 72 | public void select句(){ 73 | Parser> parser = SqlParser.select().from(SqlParser.tokenizer, SqlParser.ignored); 74 | System.out.println(parser.parse("select *")); 75 | System.out.println(parser.parse("select id, name")); 76 | System.out.println(parser.parse("select id")); 77 | } 78 | 79 | @Test 80 | public void selectsql全体(){ 81 | Parser parser = SqlParser.selectStatement().from(SqlParser.tokenizer, SqlParser.ignored); 82 | System.out.println(parser.parse("select * from shohin where id=3")); 83 | System.out.println(parser.parse("select * from shohin left join bunrui on shohin.bunrui_id=bunrui.id where id=3")); 84 | System.out.println(parser.parse("select id, name from shohin")); 85 | System.out.println(parser.parse("select * from shohin order by price")); 86 | System.out.println(parser.parse("select * from shohin where bunrui=3 order by price")); 87 | System.out.println(parser.parse("select * from shohin where bunrui=3 group by bunrui order by price")); 88 | System.out.println(parser.parse("select * from shohin where bunrui=3 group by bunrui_id having bunrui_id=3 order by price")); 89 | } 90 | 91 | @Test 92 | public void orderBy(){ 93 | Parser> parser = SqlParser.orderby().from(SqlParser.tokenizer, SqlParser.ignored); 94 | System.out.println(parser.parse("order by price")); 95 | System.out.println(parser.parse("order by price asc")); 96 | System.out.println(parser.parse("order by price desc")); 97 | System.out.println(parser.parse("order by shohin.price desc")); 98 | System.out.println(parser.parse("order by price asc, id")); 99 | } 100 | 101 | @Test 102 | public void groupBy(){ 103 | Parser> parser = SqlParser.groupby().from(SqlParser.tokenizer, SqlParser.ignored); 104 | System.out.println(parser.parse("group by bunrui_id")); 105 | System.out.println(parser.parse("group by bunrui.bunrui_id")); 106 | } 107 | 108 | @Test 109 | public void insertField(){ 110 | Parser> parser = SqlParser.insertField().from(SqlParser.tokenizer, SqlParser.ignored); 111 | System.out.println(parser.parse("(id, name)")); 112 | } 113 | 114 | @Test 115 | public void insertValue(){ 116 | Parser> parser = SqlParser.insertValues().from(SqlParser.tokenizer, SqlParser.ignored); 117 | System.out.println(parser.parse("(1, 'test')")); 118 | System.out.println(parser.parse("(3, 'test', 2, 'hoge')")); 119 | } 120 | 121 | @Test 122 | public void insert(){ 123 | Parser parser = SqlParser.insert().from(SqlParser.tokenizer, SqlParser.ignored); 124 | System.out.println(parser.parse("insert into shohin(id, name) values(1, 'hoge')")); 125 | System.out.println(parser.parse("insert into shohin values(1, 'hoge')")); 126 | System.out.println(parser.parse("insert into shohin values(1, 'hoge'), (2, 'foo')")); 127 | 128 | } 129 | @Test 130 | public void updateValue(){ 131 | Parser parser = SqlParser.updateValue().from(SqlParser.tokenizer, SqlParser.ignored); 132 | System.out.println(parser.parse("id=23")); 133 | System.out.println(parser.parse("name='abc'")); 134 | } 135 | 136 | @Test 137 | public void update(){ 138 | Parser parser = SqlParser.update().from(SqlParser.tokenizer, SqlParser.ignored); 139 | System.out.println(parser.parse("update shohin set name='test'")); 140 | System.out.println(parser.parse("update shohin set name='test' where id=2")); 141 | System.out.println(parser.parse("update shohin set name='test', seisen=1")); 142 | System.out.println(parser.parse("update shohin set name='test', seisen=1 where id=2")); 143 | System.out.println(parser.parse("update shohin set name='test', price=price+1 where id=2")); 144 | } 145 | 146 | @Test 147 | public void expression(){ 148 | Parser parser = SqlParser.expression().from(SqlParser.tokenizer, SqlParser.ignored); 149 | System.out.println(parser.parse("12+3*4")); 150 | System.out.println(parser.parse("price+3*4")); 151 | System.out.println(parser.parse("bunrui.price+3*4")); 152 | System.out.println(parser.parse("12")); 153 | System.out.println(parser.parse("price")); 154 | System.out.println(parser.parse("bunrui.price")); 155 | } 156 | Parser from(Parser parser){ 157 | return parser.from(SqlParser.tokenizer, SqlParser.ignored); 158 | } 159 | @Test 160 | public void createIndex(){ 161 | //Parser parser = from(SqlParser.createIndex()); 162 | Parser parser = SqlParser.parser(); 163 | System.out.println(parser.parse("create index on shohin(id)")); 164 | System.out.println(parser.parse("create index on shohin using hash (id)")); 165 | System.out.println(parser.parse("create index idx_shohin_id on shohin using hash (id)")); 166 | System.out.println(parser.parse("create index idx_shohin_id on shohin using hash (id, name)")); 167 | } 168 | 169 | @Test 170 | public void createTable(){ 171 | //Parser parser = from(SqlParser.createTable()); 172 | Parser parser = SqlParser.parser(); 173 | System.out.println(parser.parse("create table shohin(id, name)")); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/kis/sqlparser/StreamUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package kis.sqlparser; 8 | 9 | import java.util.stream.IntStream; 10 | import java.util.stream.Stream; 11 | import org.junit.Test; 12 | import static org.junit.Assert.*; 13 | 14 | /** 15 | * 16 | * @author naoki 17 | */ 18 | public class StreamUtilsTest { 19 | 20 | public StreamUtilsTest() { 21 | } 22 | 23 | @Test 24 | public void testSomeMethod() { 25 | StreamUtils.zip( 26 | Stream.of("abc", "cde", "def"), 27 | IntStream.iterate(1, i -> i + 1).boxed(), 28 | (s, i) -> System.out.printf("%d:%s%n", i, s)); 29 | } 30 | @Test 31 | public void testZipStream(){ 32 | StreamUtils.zip( 33 | Stream.of("abc", "cde", "def"), 34 | Stream.iterate(1, i -> i + 1)) 35 | .map(p -> p.reduce((s, i) -> String.format("%d:%s", i, s))) 36 | .forEach(s -> System.out.println(s)); 37 | } 38 | } 39 | --------------------------------------------------------------------------------