dbo CRUD first iter

This commit is contained in:
2021-11-12 15:11:03 -06:00
parent 01dd8525b1
commit d5e851c57d
32 changed files with 1964 additions and 251 deletions
+93 -34
View File
@@ -1,35 +1,47 @@
package com.reliancy.dbo;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import com.reliancy.util.CloseableIterator;
/** Description of a terminal operation with a slice of dbo objects as input or output.
* This object is not just for reading but also bulk updating.
* It will be used to describe a multi DBO read or write and to then also track results.
* At its core are action traits which are classes that define either loading,saving or deleting.
* The items field is a consumable object when consumed the action is done.
* So for loading we iterate once done it cannot be done again. Also when items are provided for saving
* once iterated over and saved they we done.
*/
public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
public static enum Type{
NONE,LOAD,SAVE,DELETE
public class Action implements Iterable<DBO>,SiphonIterator<DBO>{
public static class Trait{
public String toString(){return getClass().getSimpleName();}
}
public static class Load extends Trait{
int limit,offset;
Check filter;
}
public static class Save extends Trait{
}
public static class Delete extends Trait{
Check filter;
}
Terminal terminal;
Type type;
Trait trait;
Entity entity;
Object[] params;
CloseableIterator<DBO> items;
int limit,offset;
Condition filter;
SiphonIterator<DBO> items;
public Action(){
type=Type.NONE;
trait=null;
}
public Action(Type t){
type=t;
public Action(Trait t){
trait=t;
}
public Action(Terminal t){
terminal=t;
type=Type.NONE;
trait=null;
}
public Action execute() throws IOException{
return terminal.execute(this);
@@ -42,11 +54,11 @@ public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
this.terminal = terminal;
return this;
}
public Type getType() {
return type;
public Trait getTrait() {
return trait;
}
public Action setType(Type type) {
this.type = type;
public Action setTrait(Trait t) {
this.trait = t;
return this;
}
public Entity getEntity() {
@@ -58,22 +70,27 @@ public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
}
public void clear(){
terminal=null;
type=Type.NONE;
trait=null;
entity=null;
setItems((DBO)null);
}
public Action load(Entity ent){
type=Type.LOAD;
trait=new Load();
entity=ent;
return this;
}
public Action load(Class<? extends DBO> cls){
trait=new Load();
entity=Entity.recall(cls);
return this;
}
public Action save(Entity ent){
type=Type.SAVE;
trait=new Save();
entity=ent;
return this;
}
public Action delete(Entity ent){
type=Type.DELETE;
trait=new Delete();
entity=ent;
return this;
}
@@ -81,10 +98,10 @@ public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
params=p;
return this;
}
public Action setItems(DBO ...itms){
CloseableIterator<DBO> it=null;
public Action setItems(final DBO ...itms){
SiphonIterator<DBO> it=null;
if(itms!=null){
it=new CloseableIterator<DBO>() {
it=new SiphonIterator<DBO>() {
private int index = 0;
@Override
public boolean hasNext() {
@@ -101,7 +118,28 @@ public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
}
return setItems(it);
}
public Action setItems(CloseableIterator<DBO> itms){
public Action setItems(final Collection<DBO> itms){
SiphonIterator<DBO> it=null;
if(itms!=null){
it=new SiphonIterator<DBO>() {
private final Iterator<DBO> str = itms.iterator();
@Override
public boolean hasNext() {
return str.hasNext();
}
@Override
public DBO next() {
return str.next();
}
@Override
public void close() throws IOException {
}
};
}
return setItems(it);
}
public Action setItems(SiphonIterator<DBO> itms){
if(items==itms) return this;
if(items!=null){
try {
items.close();
@@ -111,7 +149,7 @@ public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
items=itms;
return this;
}
protected CloseableIterator<DBO> getItems(){
protected SiphonIterator<DBO> getItems(){
return items;
}
@Override
@@ -130,30 +168,51 @@ public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
public void close() throws IOException {
if(items!=null){
items.close();
items=null;
if(terminal!=null) terminal.end(this);
}
items=null;
}
public Action limit(int max) {
limit=max;
((Load)trait).limit=max;
return this;
}
public Action if_filter(Condition... c){
public Action filterBy(Check... c){
Check filter=null;
if(c!=null){
if(c.length>1) filter=Condition.and(c);
if(c.length>1) filter=Check.and(c);
else filter=c[0];
}
if(trait instanceof Load){
((Load)trait).filter=filter;
}else
if(trait instanceof Delete){
((Delete)trait).filter=filter;
}else{
filter=null;
throw new IllegalStateException("filtering not supported by trait:"+trait);
}
return this;
}
public Action if_pk(Object[] id) {
public Check getFilter(){
if(trait instanceof Load){
return ((Load)trait).filter;
}else
if(trait instanceof Delete){
return ((Delete)trait).filter;
}else{
throw new IllegalStateException("filtering not supported by trait:"+trait);
}
}
public Action if_pk(Object... id) {
Field pk=entity.getPk();
return if_filter(Condition.eq(pk,id));
return filterBy(pk.eq(id));
}
public DBO first() {
try{
return items!=null?items.next():null;
if(this.hasNext()){
return this.next();
}else{
return null;
}
}finally{
clear();
}
+217
View File
@@ -0,0 +1,217 @@
package com.reliancy.dbo;
import java.util.Iterator;
/** constraint on a field.
* conditions can be leafs or groups such as and,or,not
*/
public class Check implements Iterable<Check> {
public static abstract class Op{
public abstract boolean met(Check c,Object val);
}
public static Op AND=new Op(){
public String toString(){return "AND";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op OR=new Op(){
public String toString(){return "OR";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op NOT=new Op(){
public String toString(){return "NOT";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op EQ=new Op(){
public String toString(){return "=";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op NEQ=new Op(){
public String toString(){return "<>";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op GT=new Op(){
public String toString(){return ">";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op GTE=new Op(){
public String toString(){return ">=";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op LT=new Op(){
public String toString(){return "<";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op LTE=new Op(){
public String toString(){return "<=";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op LIKE=new Op(){
public String toString(){return "LIKE";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op IN=new Op(){
public String toString(){return "IN";}
public boolean met(Check c,Object val){
return true;
}
};
public static Op NOT_IN=new Op(){
public String toString(){return "NOT IN";}
public boolean met(Check c,Object val){
return true;
}
};
public static class CheckIterator implements Iterator<Check>{
final Check root;
Check cur;
int index;
public CheckIterator(Check ch){
root=ch;
cur=root;
index=0;
}
@Override
public boolean hasNext() {
return cur.isLeaf()==false && index<cur.args.length;
}
@Override
public Check next() {
return (Check)cur.args[index++];
}
}
public static Check and(Check... c) {
return new Check(AND,c);
}
public static Check all(Check... c) {
return new Check(AND,c);
}
public static Check or(Check... c) {
return new Check(OR,c);
}
public static Check any(Check... c) {
return new Check(OR,c);
}
public static Check not(Check... c) {
return new Check(NOT,c);
}
public static Check none(Check... c) {
return new Check(NOT,c);
}
public static Check eq(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(EQ,pk,id);
}
public static Check neq(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(NEQ,pk,id);
}
public static Check gt(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(GT,pk,id);
}
public static Check gte(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(GTE,pk,id);
}
public static Check lt(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(LT,pk,id);
}
public static Check lte(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(LTE,pk,id);
}
public static Check like(Field pk, Object... args) {
Object id=args;
if(id!=null && args.length==1) id=args[0];
return new Check(LIKE,pk,id);
}
public static Check in(Field pk, Object... id) {
return new Check(IN,pk,id);
}
public static Check not_in(Field pk, Object... id) {
return new Check(NOT_IN,pk,id);
}
Op code;
boolean leaf;
Object[] args;
boolean locked;
public Check(Op code,Field f,Object val){
this.code=code;
leaf=true;
args=new Object[]{f,val};
}
public Check(Op code,Check ... sub){
this.code=code;
leaf=false;
args=sub;
}
public Check setLocked(boolean f){
locked=f;
return this;
}
public boolean isLocked(){
return locked;
}
public Op getCode(){
return code;
}
public boolean isLeaf(){
return leaf;
}
public boolean met(Object val){
return code.met(this,val);
}
@Override
public Iterator<Check> iterator() {
return new CheckIterator(this);
}
public int getChildCount(){
return leaf?0:args.length;
}
public Check getChild(int index){
return leaf?null:(Check)args[index];
}
public Field getField(){
return (Field)args[0];
}
public Object getValue(){
return (Object)args[1];
}
public Check setValue(Object val){
if(locked) throw new IllegalStateException("check value is locked");
if(!leaf) throw new IllegalStateException("check is not a leaf");
args[1]=val;
return this;
}
}
@@ -1,81 +0,0 @@
package com.reliancy.dbo;
/** constraint on a field.
* conditions can be leafs or groups such as and,or,not
*/
public class Condition {
public static abstract class Op{
public abstract boolean met(Condition c);
}
public static Op AND=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op OR=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op NOT=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op EQ=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op NEQ=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op GT=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op GTE=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op LT=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op LTE=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op LIKE=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Op IN=new Op(){
public boolean met(Condition c){
return true;
}
};
public static Condition and(Condition... c) {
return new Condition(AND,c);
}
public static Condition eq(Field pk, Object... id) {
return new Condition(EQ,pk,id);
}
Op code;
Object[] args;
public Condition(Op code,Field f,Object val){
this.code=code;
args=new Object[]{f,val};
}
public Condition(Op code,Condition ... sub){
this.code=code;
args=sub;
}
}
+94 -9
View File
@@ -1,34 +1,119 @@
package com.reliancy.dbo;
import java.io.IOException;
import com.reliancy.rec.Hdr;
import com.reliancy.rec.JSON;
import com.reliancy.rec.Rec;
import com.reliancy.rec.Slot;
/** Instance of an entity, usually a row in a table.
*
*/
public class DBO{
public class DBO implements Rec{
public static enum Status{
NEW,USED,DELETED,COMPUTED
}
Terminal terminal;
Entity type;
Status status;
Object[] values;
public DBO() {
Class<? extends DBO> cls=this.getClass();
if(cls!=DBO.class){
Entity ent=Entity.recall(cls);
setType(ent);
}
status=Status.NEW;
}
@Override
public String toString(){
try {
StringBuffer ret=new StringBuffer();
JSON.writes(this,ret);
return ret.toString();
} catch (IOException e) {
return e.toString();
}
}
public Terminal getTerminal() {
return terminal;
}
public void setTerminal(Terminal terminal) {
public DBO setTerminal(Terminal terminal) {
this.terminal = terminal;
}
public Entity getType() {
return type;
}
public void setType(Entity type) {
this.type = type;
return this;
}
public Status getStatus(){
return status;
}
public void setStatus(Status s) {
public DBO setStatus(Status s) {
this.status = s;
return this;
}
public final Entity getType() {
return type;
}
public final DBO setType(Entity type) {
this.type = type;
if(type==null){
values=null;
}else{
values=new Object[type.count()];
}
return this;
}
@Override
public Hdr meta() {
return type;
}
@Override
public int count() {
return values!=null?values.length:0;
}
@Override
public Rec set(int pos, Object val) {
if(pos<0) pos=count()+pos;
values[pos]=val;
return this;
}
@Override
public Object get(int pos) {
if(pos<0) pos=count()+pos;
return values[pos];
}
@Override
public Rec add(Object val) {
throw new UnsupportedOperationException("dbo is not array");
}
@Override
public Rec remove(int s) {
throw new UnsupportedOperationException("dbo is not array");
}
@Override
public Rec set(Slot s, Object val) {
if(s==null) throw new IllegalArgumentException("invalid key provided");
int index=s.getPosition(); // try slot position
//if(index<0) index=type.findSlot(s.getName());// fall back to search if slot not set
if(index<0){
throw new IllegalArgumentException("invalid key provided:"+s.getName());
}else{
values[index]=val;
}
return this;
}
@Override
public Object get(Slot s, Object def) {
if(s==null) throw new IllegalArgumentException("invalid key provided");
int index=s.getPosition(); // try slot position
//if(index<0) index=type.findSlot(s.getName());// fall back to search if slot not set
if(index<0) throw new IllegalArgumentException("invalid key provided:"+s.getName());
Object ret=values[index];
return ret==null?def:ret;
}
@Override
public Rec remove(Slot s) {
throw new UnsupportedOperationException("dbo is not resizable");
}
}
+142 -6
View File
@@ -1,25 +1,52 @@
package com.reliancy.dbo;
import java.util.HashMap;
import java.util.Iterator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import com.reliancy.rec.Hdr;
import com.reliancy.rec.Slot;
/** Describes an object structure, usually a table.
*
*/
public class Entity extends Hdr{
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface Info {
String name();
}
static final HashMap<String,Entity> registry=new HashMap<>();
public static final void publish(Entity ent){
registry.put(ent.getName(),ent);
registry.put(ent.getId(),ent);
}
public static final void retract(Entity ent){
registry.values().remove(ent);
if(ent==null) return;
Collection<Entity> vals=registry.values();
while(vals.remove(ent)){}
}
public static final void retract(Class<? extends DBO> cls){
Entity ent=recall(cls.getSimpleName());
if(ent!=null){
retract(ent);
}
}
public static final Entity recall(String name){
return registry.get(name);
}
public static final Entity recall(Class<?> cls){
return recall(cls.getSimpleName());
public static final Entity recall(Class<? extends DBO> cls){
Entity ent=recall(cls.getSimpleName());
if(ent==null){
ent=publish(cls);
}
return ent;
}
/**
* this method will analyze a DBO class and forumate an Entity object out of it.
@@ -27,15 +54,124 @@ public class Entity extends Hdr{
* @return
*/
public static final Entity publish(Class<? extends DBO> cls){
return null;
Entity ret=registry.get(cls.getSimpleName());
if(ret!=null) return ret;
//System.out.println("Analyzing:"+cls);
Class base=cls.getSuperclass();
Entity base_ent=null;
int position0=0;
if(base!=null && base!=DBO.class){
base_ent=publish(base);
position0=base_ent.count();
}
java.lang.reflect.Field[] declaredFields = cls.getDeclaredFields();
ArrayList<Field> slots=new ArrayList<>();
for (java.lang.reflect.Field field : declaredFields) {
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
try {
String sf_name=field.getName();
Field slot=(Field) field.get(cls);
slot.setId(sf_name);
slot.setPosition(position0+slots.size());
slots.add(slot);
//System.out.println(sf_name+":"+slot+" atpos:"+slot.getPosition());
} catch (Exception e) {
}
}
Info info=cls.getAnnotation(Info.class);
ret=new Entity(info!=null?info.name():cls.getSimpleName()).setId(cls.getSimpleName());
ret.setBase(base_ent);
ret.setType(cls);
ret.getOwnSlots().addAll(slots);
publish(ret);
return ret;
}
Entity base;
String dbName;
String id;
Field pk;
public Entity(String name) {
super(name);
}
@Override
public Slot makeSlot(String name){
return new Field(name);
}
@Override
public Iterator<Slot> iterator(int offset){
if(offset>0) throw new IllegalArgumentException("Offset not supported");
final Entity ent=this;
return new Iterator<Slot>(){
final FieldSlice slice=new FieldSlice(ent).including(Field.FLAG_STORABLE);
@Override
public boolean hasNext() {
return slice.hasNext();
}
@Override
public Slot next() {
return slice.next();
}
};
}
@Override
public int count(){
return super.count()+(base!=null?base.count():0);
}
/**
* gets a slot which could be here or in base.
*/
@Override
public Slot getSlot(int pos){
if(base!=null){ // we got base
int ofs=base.count();
if(pos<ofs) return base.getSlot(pos);
else return super.getSlot(pos-ofs);
}else{ // regular no base
return super.getSlot(pos);
}
}
public Field getField(int index){
return (Field)getSlot(index);
}
public int getDepth(){
return base!=null?1+base.getDepth():0;
}
public Entity getBase() {
return base;
}
public Entity setBase(Entity base) {
this.base = base;
return this;
}
public String getId() {
return id;
}
public Entity setId(String id) {
this.id = id;
return this;
}
public Entity setPk(Field pk) {
this.pk = pk;
return this;
}
public Field getPk(){
if(pk!=null) return pk;
// try to locate the pk - this now gos over base as well
for(int i=0;i<count() && pk==null;i++){
Field pp=(Field) getSlot(i);
if(pp.isPk()) pk=pp;
}
return pk;
}
}
public DBO newInstance() throws InstantiationException, IllegalAccessException{
return newInstance(null).setStatus(DBO.Status.NEW);
}
public DBO newInstance(Terminal t) throws InstantiationException, IllegalAccessException{
Class<?> cls=getType();
DBO ret=(DBO) cls.newInstance();
ret.setType(this).setTerminal(t).setStatus(DBO.Status.NEW);
return ret;
}
}
+86 -1
View File
@@ -1,16 +1,101 @@
package com.reliancy.dbo;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import com.reliancy.rec.Slot;
/**
* Description of a column or property.
*/
public class Field extends Slot {
public static Field Int(String name){
return new Field(name,Integer.class);
}
public static Field Str(String name){
return new Field(name,String.class);
}
public static Field Bool(String name){
return new Field(name,Boolean.class);
}
public static Field Float(String name){
return new Field(name,Float.class);
}
public static Field Num(String name){
return new Field(name,BigDecimal.class);
}
public static Field Date(String name){
return new Field(name,Date.class);
}
public static Field DateTime(String name){
return new Field(name,Timestamp.class);
}
public static final int FLAG_PK =0x0100;
public static final int FLAG_AUTOINC =0x0200;
String id;
String typeParams;
public Field(String name) {
super(name);
this.raiseFlags(Field.FLAG_STORABLE);
}
public Field(String name,Class<?> typ) {
super(name,typ);
this.raiseFlags(Field.FLAG_STORABLE);
}
@Override
public boolean equals(String str){
return super.equals(str) || (id!=null && id.equalsIgnoreCase(str));
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean isPk() {
return checkFlags(FLAG_PK);
}
public Field setPk(boolean pk) {
if(pk) raiseFlags(FLAG_PK); else clearFlags(FLAG_PK);
return this;
}
public boolean isAutoIncrement() {
return checkFlags(FLAG_AUTOINC);
}
public Field setAutoIncrement(boolean pk) {
if(pk) raiseFlags(FLAG_AUTOINC); else clearFlags(FLAG_AUTOINC);
return this;
}
public String getTypeParams() {
return typeParams;
}
public Field setTypeParams(String p) {
typeParams=p;
return this;
}
public Check eq(Object... val) {
return Check.eq(this,val);
}
public Check neq(Object... val) {
return Check.neq(this,val);
}
public Check gt(Object... val) {
return Check.gt(this,val);
}
public Check gte(Object... val) {
return Check.gte(this,val);
}
public Check lt(Object... val) {
return Check.lt(this,val);
}
public Check lte(Object... val) {
return Check.lte(this,val);
}
public Check like(Object... val) {
return Check.like(this,val);
}
public Check in(Object... val) {
return Check.in(this,val);
}
}
@@ -0,0 +1,102 @@
package com.reliancy.dbo;
import java.util.Iterator;
import java.util.List;
/** Field iterator matching flags over entity hierarchy.
*
*/public class FieldSlice implements Iterator<Field>,Iterable<Field>{
protected final Entity entity;
protected FieldSlice sup;
protected int includeMask;
protected int excludeMask;
protected int raw_index;
protected Field next_field;
protected int next_index;
protected Entity next_entity;
public FieldSlice(Entity ent){
entity=ent;
if(entity.getBase()!=null){
sup=new FieldSlice(ent.getBase());
}else{
sup=null;
}
raw_index=-1;
next_index=-1;
}
public FieldSlice including(int mask){
includeMask=mask;
if(sup!=null) sup.including(mask);
return this;
}
public FieldSlice excluding(int mask){
excludeMask=mask;
if(sup!=null) sup.excluding(mask);
return this;
}
/** we add rewind capability to allow reuse of same fieldslice.
* i.e we use it to generate recipe, then later to properly enumerate values.
*/
public FieldSlice rewind(){
raw_index=-1;
next_index=-1;
next_field=null;
next_entity=null;
if(sup!=null) sup.rewind();
return this;
}
// search for next valid field
public final Field findNext(){
List<?> local=entity.getOwnSlots();
if(raw_index>=local.size()) return null; // we have exhausted local supply
next_field=null; // clear prev result
// search at base
if(sup!=null && sup.hasNext()){
next_field=sup.next();
next_index++;
next_entity=sup.nextEntity();
return next_field;
}
next_entity=entity;
// now search locally
for(raw_index=raw_index+1;raw_index<local.size();raw_index++){
Field f=(Field) local.get(raw_index);
int attr=f.getFlags();
if((attr & excludeMask)!=0) continue; // skip if in exluding set
if((attr & includeMask)==0) continue; // skip if not in including set
next_field=f;
next_index+=1;
break;
}
return next_field;
}
@Override
public Iterator<Field> iterator() {
return this;
}
@Override
public boolean hasNext() {
Field next=findNext();
return next!=null;
}
@Override
public Field next() {
return next_field;
}
public int nextIndex(){
return next_field!=null?next_index:-1;
}
public Entity nextEntity(){
return next_entity;
}
public DBO makeRecord() throws InstantiationException, IllegalAccessException{
return entity.newInstance();
}
public void writeRecord(DBO rec,Object val){
rec.set(next_field, val);
}
public Object readRecord(DBO rec,Object def){
return rec.get(next_field, def);
}
}
+263
View File
@@ -0,0 +1,263 @@
package com.reliancy.dbo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.reliancy.util.Handy;
public final class SQL implements Appendable{
public final static Object NULL=new Object();
public final static String WS=" ";
public final static String SELECT="SELECT";
public final static String INSERT="INSERT INTO";
public final static String UPDATE="UPDATE";
public final static String DELETE="DELETE";
public final static String FROM="FROM";
public final static String INNER_JOIN="INNER JOIN";
public final static String ON="ON";
public final static String SET="SET";
public final static String WHERE="WHERE";
final StringBuffer buffer=new StringBuffer();
final SQLTerminal terminal;
final String ql,qr;
final HashMap<Entity,String> entAlias=new HashMap<>();
public SQL(SQLTerminal terminal){
this.terminal=terminal;
ql=terminal!=null?terminal.getQuoteLeft():"\"";
qr=terminal!=null?terminal.getQuoteRight():"\"";
}
@Override
public String toString(){
return buffer.toString();
}
@Override
public final SQL append(CharSequence csq){
buffer.append(csq);
return this;
}
@Override
public final SQL append(CharSequence csq, int start, int end){
buffer.append(csq,start,end);
return this;
}
@Override
public final SQL append(char c){
buffer.append(c);
return this;
}
public final SQL select(){
append(SELECT);
return this;
}
public final SQL insert(){
append(INSERT);
return this;
}
public final SQL update(){
append(UPDATE);
return this;
}
public final SQL delete(){
append(DELETE);
return this;
}
public final SQL from(){
append(WS).append(FROM).append(WS);
return this;
}
public final SQL inner_join(){
append(WS).append(INNER_JOIN).append(WS);
return this;
}
public final SQL on(){
append(WS).append(ON).append(WS);
return this;
}
public final String wrap(String id){
if(id.startsWith(ql) && id.endsWith(qr)){
return id;
}else{
return ql+id.replace(".",qr+"."+ql)+qr;
}
}
public final SQL id(String id){
if(id.startsWith(ql) && id.endsWith(qr)){
append(id);
}else{
append(ql).append(id.replace(".",qr+"."+ql)).append(qr);
}
return this;
}
public final String getAlias(Entity e){
String eAlias=entAlias.get(e);
if(eAlias!=null) return eAlias;
eAlias="e"+entAlias.size();
entAlias.put(e,eAlias);
return eAlias;
}
public final SQL select(Entity ent,FieldSlice fit){
entAlias.clear();;
select();
while(fit.hasNext()){
int index=fit.nextIndex();
Field f=fit.next();
Entity e=fit.nextEntity();
String alias=getAlias(e);
//System.out.println("It:"+index+":/"+f+"/"+e+"/"+alias);
append(index==0?" ":",");
append(alias).append(".").id(f.getName());
}
from();
String eAlias=getAlias(ent);
id(ent.getName()).append(" ").append(eAlias);
for(Entity b=ent.getBase();b!=null;b=b.getBase()){
String bAlias=getAlias(b);
inner_join();
id(b.getName()).append(" ").append(bAlias);
on();
Field bPk=b.getPk();
Field ePk=ent.getPk();
append(eAlias).append(".").id(ePk.getName());
append("=");
append(bAlias).append(".").id(bPk.getName());
}
return this;
}
public final SQL where(){
append(WS).append(WHERE).append(WS);
return this;
}
public final SQL where(Check filter) {
append(WS).append(WHERE).append(WS).check(filter);
return this;
}
/// using entalias locate field entity and its prefix
public final String getFieldPrefix(Field f){
for(Map.Entry<Entity,String> e:entAlias.entrySet()){
Entity ent=e.getKey();
String prefix=e.getValue();
if(ent.isOwned(f)) return prefix+".";
}
return "";
}
public final SQL check(Check filter) {
if(filter.isLeaf()){
Check.Op op=filter.getCode();
Field field=filter.getField();
String fname=wrap(filter.getField().getName());
String opname=op.toString();
String arg="?";
Object val=filter.getValue();
if(op==Check.LIKE && terminal!=null && terminal.getProtocol().contains(":postgre")){
opname="ILIKE";
}
if(op==Check.IN){
arg="("+Handy.toString(val)+")";
}
if(Handy.isEmpty(val)){
// if not value then shortcuircuid condition
fname="1";
opname="=";
arg="1";
}
if(val==NULL){
arg="NULL";
if(op==Check.EQ) opname="IS";
if(op==Check.NEQ) opname="IS NOT";
}
append("(");
String fprefix=getFieldPrefix(field);
append(fprefix).append(fname).append(WS).append(opname).append(WS).append(arg);
append(")");
}else{
Check.Op op=filter.getCode();
String delim=op.toString();
if(op==Check.NOT){
append(delim).append("(").check(filter.getChild(0)).append(")");
}else{
append("(");
for(int i=0;i<filter.getChildCount();i++){
if(i>0) append(WS).append(delim).append(WS);
check(filter.getChild(i));
}
append(")");
}
}
return this;
}
/** fills params list with non-trivial parameters.
* we place this method here to be as close as possible to the one which generates the sql code.
* check and check_export must be in synch.
* @param filter
* @param params
*/
public final void check_export(Check filter,List<Object> params) {
if(filter.isLeaf()){
Check.Op op=filter.getCode();
Object val=filter.getValue();
if(Handy.isEmpty(val) || val==NULL || op==Check.IN) return; // skip over empty or NULL values
params.add(val);
}else{
for(Check ch:filter) check_export(ch,params);
}
}
/** fills check values from dbo record where equal and not-equal are used.
* we place this method here to be as close as possible to the one which generates the sql code.
* check and check_import must be in synch.
* @param filter
* @param params
*/
public final void check_import(Check filter,DBO rec) {
if(filter.isLeaf()){
if(filter.isLocked()) return; // no import on locked condition
Check.Op op=filter.getCode();
if(op!=Check.EQ && op!=Check.NEQ) return; // skip over all conditions except = and <>
Field f=filter.getField();
Object val=f.get(rec,null);
filter.setValue(val);
}else{
for(Check ch:filter) check_import(ch,rec);
}
}
public final SQL insert(Entity entity,List<Field> supplied){
insert();
append(SQL.WS).id(entity.getName()).append(" (");
StringBuffer ext=new StringBuffer();
String delim="";
Field pk=entity.getPk();
if(!entity.isOwned(pk)){
append(delim).id(pk.getName());
ext.append(delim).append("?");
delim=",";
}
for(int index=0;index<supplied.size();index++){
Field f=supplied.get(index);
if(index>0) delim=",";
append(delim).id(f.getName());
ext.append(delim).append("?");
}
append(") VALUES (").append(ext).append(")");
return this;
}
public final SQL update(Entity entity,List<Field> supplied){
update();
append(SQL.WS).id(entity.getName()).append(" SET ");
for(int index=0;index<supplied.size();index++){
Field f=supplied.get(index);
String delim=index==0?"":",";
append(delim);
id(f.getName()).append("=?");
}
where();
Field pk=entity.getPk();
id(pk.getName()).append("=?");
return this;
}
public final SQL delete(Entity entity){
delete().from().id(entity.getName());
return this;
}
}
@@ -0,0 +1,145 @@
package com.reliancy.dbo;
import java.io.Closeable;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
/** Helper object which impleents DBO deleting.
* It manages the recipe and the prepared statmenet.
* The cleaner works in two ways.
* If you supply items iterator it will delete by pk id those items.
* If you supply a Check filter and no items then it will delete based on a where statement.
*/
public class SQLCleaner implements Closeable{
protected final Entity entity;
protected final SQLTerminal terminal;
protected final SQLCleaner base; /// used for nesting
protected final SQL sql;
protected final ArrayList<Object> params;
protected Check filter;
protected Connection external;
protected PreparedStatement deleteStmt;
protected int itemsDeleted;
protected Exception error;
public SQLCleaner(Entity ent,SQLTerminal t) {
entity=ent;
terminal=t;
base=(entity.getBase()!=null)?new SQLCleaner(entity.getBase(),t):null;
sql=new SQL(terminal);
params=new ArrayList<>();
}
public SQL compileRecipe(){
if(filter==null){
// no filter we go with PK
Field pk=entity.getPk();
filter=pk.eq("?");
}
sql.delete(entity).where(filter);
return sql;
}
public boolean isLinkExternal(){
return external!=null;
}
public SQLCleaner setExternalLink(Connection link){
external=link;
return this;
}
protected Connection getExternalLink(){
return external;
}
protected Connection getInternalLink(){
try{
if(deleteStmt!=null) return deleteStmt.getConnection();
}catch(SQLException ex){
}
return null;
}
public SQLCleaner open() throws SQLException{
return open(null);
}
public SQLCleaner open(Check where) throws SQLException{
this.filter=where;
Connection link=isLinkExternal()?getExternalLink():terminal.getConnection();
if(base!=null) base.setExternalLink(link).open(filter); // definitely external link for base
SQL delSQL=compileRecipe();
//System.out.println("DEL:"+delSQL+"/"+filter);
deleteStmt=link.prepareStatement(delSQL.toString());
return this;
}
@Override
public void close() throws IOException{
if(base!=null) base.close(); // since link is external it will not close link just the rest
Connection link=getInternalLink();
if(deleteStmt!=null){
try{
deleteStmt.close();
}catch(SQLException ex){
if(error==null) error=ex;
}
}
try{
if(link!=null && !isLinkExternal()) link.close();
external=null;
}catch(SQLException ex){
if(error==null) error=ex;
}
if(error!=null){
if(error instanceof IOException) throw (IOException)error;
else throw new IOException(error);
}
}
public void flush(Iterator<DBO> items) throws SQLException {
Connection link=getInternalLink();
boolean autocommited=link.getAutoCommit();
try{
link.setAutoCommit(false);
if(items==null){ // deleting by filter
throw new SQLException("delete by filter not implemented yet");
// we would need to leave the primary filter
// we would use filter in an ID in (SUBQUERY)
// this is because filter could reference all entities and we have inheritance so multiple
// we would generate a select statement with filter and selecting only ID
}else{ // deleting by incoming records
while(items.hasNext()){
DBO rec=items.next();
deleteRecord(rec);
}
}
if(!link.getAutoCommit()){
link.commit();
}
}catch(SQLException ex){
if(!link.getAutoCommit()){
link.rollback();
}
throw ex;
}finally{
link.setAutoCommit(autocommited);
}
}
/**
* This calls one delete. It can and is called from outside in case of nesting when link is external.
* @param rec
* @throws SQLException
*/
public boolean deleteRecord(DBO rec) throws SQLException{
if(rec==null) return false;
if(base!=null) base.deleteRecord(rec); // save the superclass first
sql.check_import(filter,rec); // get values from dbo
params.clear();
sql.check_export(filter,params); // move them into params
for(int pindex=0;pindex<params.size();pindex++){
Object val=params.get(pindex);
deleteStmt.setObject(pindex+1,val);
}
int dcode=deleteStmt.executeUpdate();
itemsDeleted+=dcode;
return dcode>0;
}
}
@@ -0,0 +1,127 @@
package com.reliancy.dbo;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import com.reliancy.dbo.Action.Load;
/** SQLIterator will delay closing a connection and will iterate over result set.
*
*/
public class SQLReader implements SiphonIterator<DBO>{
protected final Entity entity;
protected final SQLTerminal terminal;
protected final SQL sql;
protected FieldSlice slice;
protected ResultSet result;
protected Exception error;
public SQLReader(Entity ent,SQLTerminal t) {
this.entity=ent;
terminal=t;
// slice controls sql fields but also lets us correctly import values later
slice=new FieldSlice(entity).including(Field.FLAG_STORABLE);
sql=new SQL(terminal);
}
public SQLReader open() throws SQLException{
return open(null);
}
public SQLReader open(Action action) throws SQLException{
error=null;
if(action==null){
sql.select(entity,slice); // simple case
}else{
compileRecipe(action); // complete case
}
//System.out.println("SQL:"+sql);
Connection link=terminal.getConnection();
PreparedStatement prep=link.prepareStatement(sql.toString());
if(action!=null){
Load tr=(Load) action.getTrait();
if(tr.filter!=null){
ArrayList<Object> params=new ArrayList<>();
sql.check_export(tr.filter, params);
for(int pindex=0;pindex<params.size();pindex++){
Object val=params.get(pindex);
prep.setObject(pindex+1,val);
}
}
}
result=prep.executeQuery();
if(link.getAutoCommit()==false) link.commit();
//action.setItems(this); -- maybe we want multiple readers on same actions - leave this to terminal
return this;
}
public SQL compileRecipe(Action action){
sql.select(action.getEntity(),slice);
Load tr=(Load) action.getTrait();
if(tr.filter!=null){
sql.where(tr.filter);
}
return sql;
}
@Override
public boolean hasNext() {
try {
return error==null?result.next():false;
} catch (SQLException e) {
error=e;
return false;
}
}
@Override
public DBO next() {
try {
DBO ret=(DBO) slice.makeRecord();
FieldSlice fit=slice.rewind();
while(fit.hasNext()){
int findex=fit.nextIndex();
//Field field=fit.next();
Object val=result.getObject(findex+1);
fit.writeRecord(ret, val);
}
ret.setStatus(DBO.Status.USED);
return ret;
} catch (Exception e) {
error=e;
return null;
}
}
@Override
public void close() throws IOException {
if(result!=null){
Statement stmt=null;
Connection link=null;
try{
stmt=result.getStatement();
link=stmt!=null?stmt.getConnection():null;
if(!result.isClosed()) result.close();
}catch(SQLException ex){
if(error==null) error=ex;
}
try{
if(stmt!=null) stmt.close();
}catch(SQLException ex){
if(error==null) error=ex;
}
try{
if(link!=null) link.close();
}catch(SQLException ex){
if(error==null) error=ex;
}
}
if(error!=null){
if(error instanceof IOException) throw (IOException)error;
else throw new IOException(error);
}
}
}
+187 -4
View File
@@ -1,8 +1,15 @@
package com.reliancy.dbo;
import java.io.IOException;
import java.lang.reflect.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.JDBCType;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import com.reliancy.util.Path;
import com.zaxxer.hikari.HikariConfig;
@@ -12,6 +19,9 @@ public class SQLTerminal implements Terminal{
HikariConfig config = new HikariConfig();
HikariDataSource ds;
Path url;
String quoteLeft="\""; // quotes could be subject to sql flavour
String quoteRight="\"";
public SQLTerminal(String url){
this.url=new Path(url);
String proto=this.url.getProtocol();
@@ -20,17 +30,190 @@ public class SQLTerminal implements Terminal{
config.setJdbcUrl(u);
config.setUsername(this.url.getUserid());
config.setPassword(this.url.getPassword());
//config.setAutoCommit(false); -- do this in batch cases only
config.addDataSourceProperty( "cachePrepStmts" , "true" );
config.addDataSourceProperty( "prepStmtCacheSize" , "250" );
//config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );
config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );
ds = new HikariDataSource( config );
}
public Connection getConnection() throws SQLException{
return ds.getConnection();
}
@Override
public Action execute(Action q) throws IOException {
return q;
public Action execute(Action q) throws IOException{
System.out.println("Executing..."+q.getTrait());
Action.Trait tr=q.getTrait();
if(tr instanceof Action.Load){
Entity ent=q.getEntity();
SQLReader reader=new SQLReader(ent,this);
try {
reader.open(q);
q.setItems(reader);
} catch (SQLException e) {
reader.close();
throw new IOException(e);
}
System.out.println("Executing...Done");
return q;
}else if(tr instanceof Action.Save){
Entity ent=q.getEntity();
try(SQLWriter writer=new SQLWriter(ent,this)) {
writer.open();
writer.flush(q.getItems());
System.out.println("Executing...Done");
return q;
}catch(SQLException e){
throw new IOException(e);
}
}else if(tr instanceof Action.Delete){
Entity ent=q.getEntity();
try(SQLCleaner cleaner=new SQLCleaner(ent,this)) {
cleaner.open();
cleaner.flush(q.getItems());
System.out.println("Executing...Done");
return q;
}catch(SQLException e){
throw new IOException(e);
}
}else{
throw new UnsupportedOperationException("Trait not supported:"+tr);
}
}
public String getProtocol() {
return url.getProtocol();
}
public String getQuoteLeft(){
return this.quoteLeft;
}
public String getQuoteRight(){
return this.quoteRight;
}
final HashMap<Integer,Class<?>> sql2java=new HashMap<>();
final HashMap<Class<?>,Integer> java2sql=new HashMap<>();
public Map<Class<?>,Integer> getJava2SQL(){
if(!java2sql.isEmpty()) return java2sql;
String protocol=url.getProtocol();
java2sql.put(java.math.BigDecimal.class,Types.DECIMAL);
java2sql.put(java.math.BigInteger.class,Types.DECIMAL);
java2sql.put(Boolean.class,protocol.contains(":oracle")?Types.INTEGER:Types.BOOLEAN);
java2sql.put(Byte.class,Types.TINYINT);
java2sql.put(Short.class,Types.SMALLINT);
java2sql.put(Integer.class,Types.INTEGER);
java2sql.put(Long.class,Types.BIGINT);
java2sql.put(Float.class,Types.FLOAT);
java2sql.put(Double.class,Types.DOUBLE);
java2sql.put(byte[].class,Types.VARBINARY);
java2sql.put(Blob.class,Types.BLOB);
java2sql.put(char[].class,Types.VARCHAR);
java2sql.put(String.class,Types.VARCHAR);
java2sql.put(StringBuffer.class,Types.VARCHAR);
java2sql.put(Clob.class,Types.CLOB);
java2sql.put(java.sql.Date.class,Types.DATE);
java2sql.put(java.sql.Time.class,Types.TIME);
java2sql.put(java.sql.Timestamp.class,Types.TIMESTAMP);
java2sql.put(Array.class,Types.ARRAY);
return java2sql;
}
public Map<Integer,Class<?>> getSQL2Java(){
if(!sql2java.isEmpty()) return sql2java;
//String protocol=url.getProtocol();
sql2java.put(Types.NUMERIC,java.math.BigDecimal.class);
sql2java.put(Types.DECIMAL,java.math.BigDecimal.class);
sql2java.put(Types.BIT,Boolean.class);
sql2java.put(Types.BOOLEAN,Boolean.class);
sql2java.put(Types.TINYINT,Byte.class);
sql2java.put(Types.SMALLINT,Short.class);
sql2java.put(Types.INTEGER,Integer.class);
sql2java.put(Types.BIGINT,Long.class);
sql2java.put(Types.REAL,Float.class);
sql2java.put(Types.FLOAT,Float.class);
sql2java.put(Types.DOUBLE,Double.class);
sql2java.put(Types.BINARY,byte[].class);
sql2java.put(Types.VARBINARY,byte[].class);
sql2java.put(Types.LONGVARBINARY,byte[].class);
sql2java.put(Types.CHAR,String.class);
sql2java.put(Types.NCHAR,String.class);
sql2java.put(Types.VARCHAR,String.class);
sql2java.put(Types.NVARCHAR,String.class);
sql2java.put(Types.LONGVARCHAR,String.class);
sql2java.put(Types.LONGNVARCHAR,String.class);
sql2java.put(Types.DATE,java.sql.Date.class);
sql2java.put(Types.TIME,java.sql.Time.class);
sql2java.put(Types.TIMESTAMP,java.sql.Timestamp.class);
sql2java.put(Types.BLOB,byte[].class);
sql2java.put(Types.CLOB,char[].class);
sql2java.put(Types.ARRAY,java.sql.Array.class);
sql2java.put(Types.JAVA_OBJECT,Object.class);
return sql2java;
}
/**
* Returns back java class for given id and or name.
* The name is not used in default implementation.
* @param typeid
*/
public Class<?> getJavaType(int typeid) {
Class<?> ret=getSQL2Java().get(typeid);
return ret;
}
/**
* This method will correct cases when sqltype is varchar (12) but type name is date or similar.
* @param sqltype
* @param type_name
* @return tries to promote sqltype given type name to something more specific.
*/
public int getTypeId(int sqltype,String type_name){
if(type_name==null) return sqltype;
type_name=type_name.toLowerCase();
if(sqltype==Types.VARCHAR || sqltype==Types.CHAR){
if(type_name.equals("date")) sqltype=Types.DATE;
if(type_name.equals("time")) sqltype=Types.TIME;
if(type_name.equals("datetime")) sqltype=Types.TIMESTAMP;
}
return sqltype;
}
/**
* @param cls
* @param createParams
* @return SQL type given java class and create params
*/
public int getTypeId(Class<?> cls,String createParams){
int ret=getJava2SQL().get(cls);
return ret;
}
public String getTypeName(Class<?> cls,String createParams){
int id=this.getTypeId(cls, createParams);
String ret = JDBCType.valueOf(id).getName();
if(ret==null) return null;
String protocol=url.getProtocol();
if(protocol.contains(":sqlserver")){
if("boolean".equalsIgnoreCase(ret)) ret="BIT";
if("timestamp".equalsIgnoreCase(ret)) ret="DATETIME";
if("double".equalsIgnoreCase(ret)) ret="float";
if("float".equalsIgnoreCase(ret)) ret="real";
}
if(protocol.contains(":postgre")){
if("varbinary".equalsIgnoreCase(ret)) ret="bytea";
if("double".equalsIgnoreCase(ret)) ret="double precision";
}
if("varchar".equalsIgnoreCase(ret) && (createParams!=null && !createParams.isEmpty())){
long size=Long.parseLong(createParams);
if(protocol.contains(":sqlserver")) ret=size>8000?ret.concat("(").concat("MAX").concat(")"):ret.concat("(").concat(String.valueOf(size)).concat(")");
else if(protocol.contains(":oracle")) ret=size>2000?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")");
else if(protocol.contains(":mysql")) ret=size>Character.MAX_VALUE?"TEXT":ret.concat("(").concat(String.valueOf(size)).concat(")");
else if(protocol.contains(":h2")) ret=size>Integer.MAX_VALUE?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")");
else if(protocol.contains(":postgre")) ret=size>Character.MAX_VALUE?"TEXT":ret.concat("(").concat(String.valueOf(size)).concat(")");
else ret=(size>Character.MAX_VALUE)?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")");
}
String args=null;
if(ret.indexOf('(')==-1 && createParams!=null && !createParams.isEmpty()){
if("decimal".equalsIgnoreCase(ret)) args=createParams;
if("numeric".equalsIgnoreCase(ret)) args=createParams;
}
if(args!=null){
ret=ret.concat("(").concat(args).concat(")");
}
return ret;
}
}
@@ -0,0 +1,208 @@
package com.reliancy.dbo;
import java.io.Closeable;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import com.reliancy.util.Handy;
/** Helper object which impleents DBO saving. It manages the recipe and the prepared statmenet.
* Also keeps track of which fields are sent down to DB.
* The writer does the work in flush method during which it exhausts the items. While in flush it
* will disable autocommit and enable it at the end. We could call flush multiple times to send multiple
* batches down to DB.
* Initially we would ctor with Action object but actually we need to generate writes for different entities
* especially in inheritance cases.
*/
public class SQLWriter implements Closeable{
protected final Entity entity;
protected final SQLTerminal terminal;
protected final SQLWriter base; /// used for nesting
protected final ArrayList<Field> supplied=new ArrayList<Field>();
protected final ArrayList<Field> generated=new ArrayList<Field>();
protected String insertSQL;
protected String updateSQL;
protected Connection external;
protected PreparedStatement insertStmt;
protected PreparedStatement updateStmt;
protected int itemsInserted;
protected int itemsUpdated;
protected Exception error;
public SQLWriter(Entity ent,SQLTerminal t) {
entity=ent;
terminal=t;
base=(entity.getBase()!=null)?new SQLWriter(entity.getBase(),t):null;
// we select proper fields for this entity
FieldSlice slice=new FieldSlice(entity).including(Field.FLAG_STORABLE); // includes all even autoincrement
while(slice.hasNext()){
Field f=slice.next();
Entity e=slice.nextEntity();
if(e!=entity) continue; // skip if not part of this entity
if(f.isAutoIncrement()){
generated.add(f);
}else{
supplied.add(f);
}
}
}
public String compileInsertRecipe(){
if(insertSQL!=null) return insertSQL;
SQL buf=new SQL(terminal);
buf.insert(entity,supplied);
insertSQL=buf.toString();
return insertSQL;
}
public String compileUpdateRecipe(){
if(updateSQL!=null) return updateSQL;
SQL buf=new SQL(terminal);
buf.update(entity,supplied);
updateSQL=buf.toString();
return updateSQL;
}
public boolean isLinkExternal(){
return external!=null;
}
public SQLWriter setExternalLink(Connection link){
external=link;
return this;
}
protected Connection getExternalLink(){
return external;
}
protected Connection getInternalLink(){
try{
if(insertStmt!=null) return insertStmt.getConnection();
if(updateStmt!=null) return updateStmt.getConnection();
}catch(SQLException ex){
}
return null;
}
public SQLWriter open() throws SQLException{
Connection link=isLinkExternal()?getExternalLink():terminal.getConnection();
if(base!=null) base.setExternalLink(link).open(); // definitely external link for base
String inSql=compileInsertRecipe();
String upSql=compileUpdateRecipe();
//System.out.println("INS:"+inSql);
//System.out.println("UPD:"+upSql);
String[] genkeys=new String[generated.size()];
for(int i=0;i<generated.size();i++){
Field f=generated.get(i);
genkeys[i]=f.getName();
}
insertStmt=link.prepareStatement(inSql,genkeys);
updateStmt=link.prepareStatement(upSql);
//result=prep.executeQuery();
//if(link.getAutoCommit()==false) link.commit();
return this;
}
@Override
public void close() throws IOException{
if(base!=null) base.close(); // since link is external it will not close link just the rest
Connection link=getInternalLink();
if(insertStmt!=null){
try{
insertStmt.close();
}catch(SQLException ex){
if(error==null) error=ex;
}
}
if(updateStmt!=null){
try{
updateStmt.close();
}catch(SQLException ex){
if(error==null) error=ex;
}
}
try{
if(link!=null && external!=link) link.close();
external=null;
}catch(SQLException ex){
if(error==null) error=ex;
}
if(error!=null){
if(error instanceof IOException) throw (IOException)error;
else throw new IOException(error);
}
}
public void flush(Iterator<DBO> items) throws SQLException {
Connection link=isLinkExternal()?getExternalLink():getInternalLink();
boolean autocommited=link.getAutoCommit();
try{
link.setAutoCommit(false);
while(items.hasNext()){
DBO rec=items.next();
writeRecord(rec);
}
if(!link.getAutoCommit()){
link.commit();
}
}catch(SQLException ex){
if(!link.getAutoCommit()){
link.rollback();
}
throw ex;
}finally{
link.setAutoCommit(autocommited);
}
}
/**
* This calls one update/insert. It can and is called from outside in case of nesting when link is external.
* @param rec
* @throws SQLException
*/
public boolean writeRecord(DBO rec) throws SQLException{
if(base!=null) base.writeRecord(rec); // save the superclass first
// select mode
int pindex=0;
Field pk=entity.getPk();
boolean pk_owned=entity.isOwned(pk);
PreparedStatement stmt=null;
if(rec.getStatus()==DBO.Status.NEW){
stmt=insertStmt;
// need to inject pk here is not owned
if(!pk_owned) stmt.setObject(++pindex,pk.get(rec,null),terminal.getTypeId(pk.getType(),pk.getTypeParams()));
}
if(rec.getStatus()==DBO.Status.USED){
stmt=updateStmt;
// update has a pk condition for sure
stmt.setObject(supplied.size()+1,pk.get(rec,null),terminal.getTypeId(pk.getType(),pk.getTypeParams()));
}
if(stmt==null) return false;
// copy values
for(int index=0;index<supplied.size();index++){
Field f=supplied.get(index);
pindex+=1;
int tid=terminal.getTypeId(f.getType(),f.getTypeParams());
Object val=f.get(rec,null);
//System.out.println("Param:"+pindex+":"+f.getName()+":"+val);
stmt.setObject(pindex,val,tid);
}
int ucode=stmt.executeUpdate();
//System.out.println("UCode:"+ucode);
if(rec.getStatus()==DBO.Status.NEW){
this.itemsInserted+=ucode;
if(ucode>0 && !generated.isEmpty()){
try (ResultSet keys = stmt.getGeneratedKeys()) {
if(keys.next()){
for(int i=0;i<generated.size();i++){
Field f=generated.get(i);
Object autoval=keys.getObject(i+1);
f.set(rec,autoval);
}
}
}
}
}
if(rec.getStatus()==DBO.Status.USED){
this.itemsUpdated+=ucode;
}
return ucode>0;
}
}
@@ -0,0 +1,9 @@
package com.reliancy.dbo;
import java.io.Closeable;
import java.util.Iterator;
/** Iterator interface suitable for dbo resultsets.
*
*/
public interface SiphonIterator<T> extends Iterator<T>, Closeable {
}
@@ -25,6 +25,13 @@ public interface Terminal {
public default Terminal meta(Entity ent){
return null;
}
public default <T extends DBO> T load(Class<T> cls,Object...id) throws IOException {
Entity ent=Entity.recall(cls);
String sig="/"+ent.getName()+"/load";
try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){
return (T)act.first();
}
}
public default DBO load(Entity ent,Object...id) throws IOException {
String sig="/"+ent.getName()+"/load";
try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){
@@ -39,6 +46,7 @@ public interface Terminal {
}
}
public default boolean delete(DBO rec) throws IOException {
if(rec==null) return false;
Entity ent=rec.getType();
String sig="/"+ent.getName()+"/delete";
try(Action act=begin(sig).delete(ent).setItems(rec).execute()){
+7 -2
View File
@@ -47,8 +47,13 @@ public final class HTTP {
public static String guess_mime(Object ret) {
if(ret instanceof CharSequence){
CharSequence retstr=(CharSequence)ret;
if(retstr.length()>0 && retstr.charAt(0)=='<') return "text/html";
if(retstr.length()>0 && "{[".indexOf(retstr.charAt(0))!=-1) return "application/json";
for(int index=0;index<retstr.length();index++){
char ch=retstr.charAt(index);
if(Character.isWhitespace(ch)) continue;
if(ch=='<') return "text/html";
if(ch=='{' || ch=='[') return "application/json";
break;
}
return "text/plain";
}
if(ret instanceof byte[]){
+1 -1
View File
@@ -206,7 +206,7 @@ public class Router extends AbstractHandler{
String ret="";
try {
Template.search_path("./var",SecurityPolicy.class);
Template t=Template.find("resources/login.j2");
Template t=Template.find("/templates/login.hbs");
System.out.println("Template:"+t);
ret = t.render(context).toString();
} catch (IOException e) {
@@ -1,4 +0,0 @@
{% extends "base.j2" %}
{% block content %}
<div>Hello from code: {{name}}!</div>
{% endblock %}
+48 -24
View File
@@ -1,16 +1,23 @@
package com.reliancy.rec;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Iterator;
import java.util.List;
/** Base class of meta objects.
* We use it to describe certain meta information. We derive from it Slot.
* We define keys list of slots on the header level to describe slots.
* We define keys list of slots on the header level to describe sub-slots.
*
* This class describes structure of Fields or Entities via the keys array of slots.
* Additionally we provide a number of methods to locate, set or get or remove or add slots.
* However slots could reside in other places such as base classes and so getOwnSlots will return a
* bare list of slots in this object while all other methods will take into account other sources.
* We do this to stay consistent at Rec level when Hdr inheritance comes into play.
*/
public class Hdr {
public static final int FLAG_ARRAY =0x0001;
public static final int FLAG_CHANGED =0x0002;
public static final int FLAG_HIDDEN =0x0004;
public static final int FLAG_STORABLE =0x0004;
public static final int FLAG_LOCKED =0x0008;
int flags;
String name;
@@ -30,8 +37,11 @@ public class Hdr {
@Override
public String toString(){
StringBuilder ret=new StringBuilder();
ret.append("{").append("flags:").append(flags).append(",name:").append(name);
ret.append(",dim:").append(keys.size()).append("}");
ret.append(name).append(":");
ret.append("{")
.append("flags:").append(flags)
.append(",dim:").append(count())
.append("}");
return ret.toString();
}
@@ -53,6 +63,9 @@ public class Hdr {
public void setType(Class<?> type) {
this.type = type;
}
public int getFlags(){
return flags;
}
public Hdr raiseFlags(int f){
flags|=f;
return this;
@@ -67,36 +80,51 @@ public class Hdr {
public <T extends Hdr> T castAs(Class<T> clazz){
return clazz.cast(this);
}
public int findSlot(String name){
return findSlot(name,0);
public List<Slot> getOwnSlots(){
return keys;
}
public int findSlot(String name,int ofs){
ListIterator<Slot> it=keys.listIterator(ofs);
public boolean isOwned(Slot s){
return keys.contains(s);
}
public Iterator<Slot> iterator(int offset){
return keys.listIterator(offset);
}
public int indexOf(String name){
return indexOf(name,0);
}
public int indexOf(String name,int ofs){
Iterator<Slot> it=iterator(ofs);
int index=-1;
while(it.hasNext()){
int index=it.nextIndex();
index+=1;
Slot e=it.next();
if(e.getName().equalsIgnoreCase(name)) return index;
//if(e.getName().equalsIgnoreCase(name)) return index;
if(e.equals(name)) return index;
}
return -1;
}
public int findSlot(Slot s,int ofs){
ListIterator<Slot> it=keys.listIterator(ofs);
public int indexOf(Slot s,int ofs){
Iterator<Slot> it=iterator(ofs);
int index=-1;
while(it.hasNext()){
int index=it.nextIndex();
index+=1;
Slot e=it.next();
if(e==s) return index;
}
return -1;
}
/**
public Slot makeSlot(String name){
return new Slot(name);
}
/**
* this version will get or create a slot by given name.
* @param name
* @return
*/
public Slot getSlot(String name){
int index=findSlot(name);
public Slot getSlot(String name,boolean make){
int index=indexOf(name);
if(index<0){
return new Slot(name);
return make?makeSlot(name):null;
}else{
return getSlot(index);
}
@@ -116,11 +144,7 @@ public class Hdr {
keys.set(index,s);
return this;
}
public Slot[] slots(Slot... slots){
if(slots!=null && slots.length>0){
keys.clear();
for(int i=0;i<slots.length;i++) keys.add(slots[i]);
}
return keys.toArray(new Slot[keys.size()]);
public int count(){
return keys.size();
}
}
+8 -1
View File
@@ -17,6 +17,13 @@ public class JSON {
public static final void writes(Rec rec,Appendable sink) throws IOException{
JSONEncoder.encode(rec, sink);
}
public static final String toString(Rec rec){
StringBuffer buf=new StringBuffer();
try {
writes(rec,buf);
} catch (IOException e) {
}
return buf.toString();
}
}
@@ -189,7 +189,7 @@ public class JSONEncoder{
if (o != null) {
o.append(val.isArray()?"[":"{");
}
for (int i=0;i<val.count();i++) {
for (int i=0;i<val.count();i++) {
Slot k=val.getSlot(i);
Object v=val.get(i);
if (i > 0) {
+3 -3
View File
@@ -118,7 +118,7 @@ public class Obj implements Rec{
if(s==null) throw new IllegalArgumentException("invalid key provided");
if(isArray()) throw new IllegalStateException("array not mappable with:"+s.getName());
int index=s.getPosition(); // try slot position
if(index<0) index=meta.findSlot(s.getName());// fall back to search if slot not set
if(index<0) index=meta.indexOf(s.getName());// fall back to search if slot not set
if(index<0){
values.add(val);
meta.addSlot(s);
@@ -138,14 +138,14 @@ public class Obj implements Rec{
if(s==null) throw new IllegalArgumentException("invalid key provided");
//if(keys==null) throw new IllegalStateException("array not mappable with:"+s.getName());
int index=s.getPosition(); // try slot position
if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set
if(index<0 && !isArray()) index=meta.indexOf(s.getName());// fall back to search if slot not set
return index<0?def:values.get(index);
}
@Override
public Rec remove(Slot s) {
int index=s.getPosition(); // try slot position
if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set
if(index<0 && !isArray()) index=meta.indexOf(s.getName());// fall back to search if slot not set
if(index>=0) remove(index);
return this;
}
+1 -1
View File
@@ -11,7 +11,7 @@ public interface Rec extends Vec{
public Rec remove(Slot s);
public default Slot getSlot(String name){
Hdr m=meta();
return m!=null?m.getSlot(name):null;
return m!=null?m.getSlot(name,true):null;
}
public default Slot getSlot(int pos){
Hdr m=meta();
+12 -6
View File
@@ -11,7 +11,7 @@ public class Slot extends Hdr {
Object getInitalValue(Slot s,Rec rec);
}
public static final Initializer DEFAULT_INITIALIZER=new Initializer(){
public Object getInitalValue(Slot s,Rec rec) {return s.getDefaultValue();}
public Object getInitalValue(Slot s,Rec rec) {return s.getInitValue();}
};
int position;
Object defaultValue;
@@ -25,23 +25,29 @@ public class Slot extends Hdr {
this.position=-1;
this.initValue=DEFAULT_INITIALIZER;
}
public boolean equals(String str){
return name.equalsIgnoreCase(str);
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
public Slot setPosition(int position) {
this.position = position;
return this;
}
public Object getDefaultValue() {
public Object getInitValue() {
return defaultValue;
}
public void setDefaultValue(Object defaultValue) {
public Slot setInitValue(Object defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public Initializer getInitValue() {
public Initializer getInitVia() {
return initValue;
}
public void setInitValue(Initializer initValue) {
public Slot setInitVia(Initializer initValue) {
this.initValue = initValue;
return this;
}
public int toString(Object val, StringBuilder buf) {
int length0=buf.length();
@@ -1,7 +0,0 @@
package com.reliancy.util;
import java.io.Closeable;
import java.util.Iterator;
public interface CloseableIterator<T> extends Iterator<T>, Closeable {
}
+28 -2
View File
@@ -45,7 +45,7 @@ public final class Handy {
if(clazz.isAssignableFrom(val.getClass())) return clazz; // we are assignable
if(val instanceof String){
String value=(String) val;
if(value.isBlank() || value.equals("''") || value.equals("\"\"")) return null;
if(value.isEmpty() || value.equals("''") || value.equals("\"\"")) return null;
if( Boolean.class==( clazz ) || boolean.class==( clazz ) ) return Boolean.parseBoolean( value );
if( Byte.class==( clazz ) || byte.class==( clazz ) ) return Byte.parseByte( value );
if( Short.class==( clazz ) || short.class==( clazz ) ) return Short.parseShort( value );
@@ -454,5 +454,31 @@ public final class Handy {
all.toArray(ret);
return ret;
}
public static String toString(Object...args){
StringBuilder buf=new StringBuilder();
if(args.length>1){
buf.append("[");
for(int i=0;i<args.length;i++) buf.append(i==0?"":",").append(toString(args[i]));
buf.append("]");
}else if(args.length==1){
Object arg=args[0];
if(arg instanceof Iterable){
java.util.Iterator<?> it=((Iterable<?>)arg).iterator();
buf.append("[");
while(it.hasNext()) buf.append(buf.length()>1?",":"").append(it.next());
buf.append("]");
}else
if(arg instanceof java.util.Map){
java.util.Map<?,?> marg=(Map<?,?>) arg;
buf.append("{");
for(java.util.Map.Entry<?,?>e:marg.entrySet()){
buf.append(e.getKey().toString()).append(":").append(toString(e.getValue()));
}
buf.append("}");
}else{
buf.append(String.valueOf(arg));
}
}
return buf.toString();
}
}
+36 -4
View File
@@ -1,20 +1,28 @@
package com.reliancy.util;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.io.AbstractTemplateLoader;
import com.github.jknack.handlebars.io.TemplateSource;
import com.github.jknack.handlebars.io.URLTemplateSource;
/*
import com.hubspot.jinjava.Jinjava;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.loader.ResourceLocator;
*/
/**
* We will manage template rendering thru this class.
*/
public class Template {
/*
static Jinjava jinjava;
static{
jinjava = new Jinjava();
@@ -32,6 +40,27 @@ public class Template {
}
}
}
*/
public static class HBLoader extends AbstractTemplateLoader{
public HBLoader(){
this.setPrefix("/templates/");
}
@Override
public TemplateSource sourceAt(String location) throws IOException {
String fullpath=this.resolve(location);
URL loc=Resources.findFirst(null,fullpath,Template.search_path);
System.out.println(location+":"+loc+":"+fullpath);
if (loc == null) {
Logger.getLogger(Template.class.getSimpleName()).warning("Missing template"+fullpath);
throw new FileNotFoundException(location);
}
return new URLTemplateSource(location,loc);
}
}
static Handlebars handlebars = new Handlebars(new HBLoader());
static Object[] search_path;
static HashMap<String,Template> cache=new HashMap<>();
/** renders a template to string, possibly locates it first.
@@ -56,7 +85,6 @@ public class Template {
Template ret=cache.get(path);
if(ret!=null) return ret;
URL loc=Resources.findFirst(null, path, (sp!=null && sp.length>0?sp:search_path));
System.out.println("TLOCL:"+loc);
if(loc==null) return null;
ret=new Template(loc);
cache.put(path,ret);
@@ -66,7 +94,7 @@ public class Template {
if(sp!=null && sp.length>0) search_path=sp;
return search_path;
}
com.github.jknack.handlebars.Template recipe;
final URL location;
String source;
public Template(URL location){
@@ -88,7 +116,11 @@ public class Template {
}
public CharSequence render(Map<String,?> context) throws IOException{
if(source==null) load();
String ret = jinjava.render(source, context);
//String ret = jinjava.render(source, context);
if(recipe==null){
recipe=handlebars.compileInline(source);
}
String ret=recipe.apply(context);
return ret;
}
}
+4
View File
@@ -0,0 +1,4 @@
{{#partial "content"}}
Calling base
{{/partial}}
{{> base}}
@@ -4,25 +4,67 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Date;
import com.reliancy.rec.JSON;
import org.junit.BeforeClass;
import org.junit.Test;
public class TerminalTest {
@Entity.Info(
name="dbo.Maps"
)
public static class Maps extends DBO{
public static Field map_id=new Field("Map_id",Integer.class);
public static Field map_name=new Field("Map_name",String.class);
public static Field map_id=Field.Int("Map_id").setPk(true);
public static Field map_name=Field.Str("Map_name");
public static Field created=Field.DateTime("Created");
public static Field active=Field.Bool("Active");
static{
Entity.publish(Maps.class);
//Entity.publish(Maps.class);
}
}
@Entity.Info(
name="public.securable"
)
public static class Securable extends DBO{
public static Field id=Field.Int("id").setPk(true).setAutoIncrement(true);
public static Field kind=Field.Str("kind");
public static Field name=Field.Str("name");
public static Field display_name=Field.Str("display_name");
public static Field created=Field.DateTime("created_on");
public static Field is_essential=Field.Bool("is_essential");
static{
//Entity.publish(Maps.class);
}
}
@Entity.Info(
name="public.product"
)
public static class Product extends Securable{
public static Field valid_since=Field.DateTime("valid_since");
public static Field valid_until=Field.DateTime("valid_until");
public static Field short_info=Field.Str("short_info");
}
static SQLTerminal t;
@BeforeClass
public static void beforeAllTestMethods() {
System.out.println("Invoked once before all test methods");
String url="jdbc:postgresql://postgres:Ramudin99@bigbang:5432/Test";
t=new SQLTerminal(url);
}
/**
* Plain CRUD
* jdbc connectivity
* @throws IOException
* @throws SQLException
*/
@Test
public void connection() throws IOException, SQLException{
String url="jdbc:postgresql://postgres:Ramudin99@bigbang:5432/Test";
SQLTerminal t=new SQLTerminal(url);
try(Connection c=t.getConnection()){
System.out.println("Connection:"+c);
try (Statement stmt = c.createStatement()) {
@@ -38,5 +80,38 @@ public class TerminalTest {
}
}
}
@Test
public void simpleCRUD() throws IOException, SQLException{
System.out.println("SimpleCRUD");
try(Action act=t.begin().load(Maps.class).execute()){
for(DBO o:act){
System.out.println("DBO:"+o);
}
}
Entity.retract(Maps.class);
}
@Test
public void complexCRUD() throws IOException, SQLException{
System.out.println("ComplexCRUD");
try(Action act=t.begin().load(Product.class).execute()){
for(DBO o:act){
System.out.println("DBO:"+o);
}
}
Product p=new Product();
p.setStatus(DBO.Status.USED);
Product.kind.set(p,Product.class.getSimpleName());
Product.name.set(p,"myproduct");
Product.created.set(p,new Date());
Product.short_info.set(p,"a sweet melody");
Product.display_name.set(p,"first entry");
System.out.println("P0:"+JSON.toString(p));
t.save(p);
System.out.println("P1:"+JSON.toString(p));
Product pp=t.load(Product.class, 35);
System.out.println("Returning:"+pp);
//t.delete(pp);
Entity.retract(Maps.class);
}
}