diff --git a/morel b/morel index 700a7fd1..9bc330dc 100755 --- a/morel +++ b/morel @@ -52,6 +52,9 @@ fi export JAVA_OPTS=-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl -exec java $VM_OPTS -cp "${CP}" $JAVA_OPTS net.hydromatic.morel.Shell --foreign='net.hydromatic.morel.BuiltInDataSet$Dictionary' "$@" +exec java $VM_OPTS -cp "${CP}" $JAVA_OPTS net.hydromatic.morel.Shell \ + --foreign='net.hydromatic.morel.BuiltInDataSet$Dictionary' \ + --directory="$(pwd)" \ + "$@" # End morel diff --git a/src/main/java/net/hydromatic/morel/Main.java b/src/main/java/net/hydromatic/morel/Main.java index 7a7a2c89..f5db7439 100644 --- a/src/main/java/net/hydromatic/morel/Main.java +++ b/src/main/java/net/hydromatic/morel/Main.java @@ -27,6 +27,7 @@ import net.hydromatic.morel.compile.Tracer; import net.hydromatic.morel.compile.Tracers; import net.hydromatic.morel.eval.Codes; +import net.hydromatic.morel.eval.Prop; import net.hydromatic.morel.eval.Session; import net.hydromatic.morel.foreign.ForeignValue; import net.hydromatic.morel.parse.MorelParserImpl; @@ -69,9 +70,8 @@ public class Main { private final boolean echo; private final Map valueMap; final TypeSystem typeSystem = new TypeSystem(); - final File directory; final boolean idempotent; - final Session session = new Session(); + final Session session; /** Command-line entry point. * @@ -102,7 +102,9 @@ public Main(List argList, Reader in, Writer out, this.out = buffer(out); this.echo = argList.contains("--echo"); this.valueMap = ImmutableMap.copyOf(valueMap); - this.directory = requireNonNull(directory, "directory"); + final Map map = new LinkedHashMap<>(); + Prop.DIRECTORY.set(map, requireNonNull(directory, "directory")); + this.session = new Session(map); this.idempotent = idempotent; } @@ -311,7 +313,8 @@ static class SubShell extends Shell { outLines.accept("[opening " + fileName + "]"); File file = new File(fileName); if (!file.isAbsolute()) { - file = new File(main.directory, fileName); + final File directory = Prop.DIRECTORY.fileValue(main.session.map); + file = new File(directory, fileName); } if (!file.exists()) { outLines.accept("[use failed: Io: openIn failed on " diff --git a/src/main/java/net/hydromatic/morel/Shell.java b/src/main/java/net/hydromatic/morel/Shell.java index b95f9e13..505e86d6 100644 --- a/src/main/java/net/hydromatic/morel/Shell.java +++ b/src/main/java/net/hydromatic/morel/Shell.java @@ -28,6 +28,7 @@ import net.hydromatic.morel.compile.Tracer; import net.hydromatic.morel.compile.Tracers; import net.hydromatic.morel.eval.Codes; +import net.hydromatic.morel.eval.Prop; import net.hydromatic.morel.eval.Session; import net.hydromatic.morel.foreign.Calcite; import net.hydromatic.morel.foreign.DataSet; @@ -259,8 +260,10 @@ public void run() { pause(); final TypeSystem typeSystem = new TypeSystem(); + final Map map = new LinkedHashMap<>(); + Prop.DIRECTORY.set(map, config.directory); + final Session session = new Session(map); Environment env = Environments.env(typeSystem, config.valueMap); - final Session session = new Session(); final LineFn lineFn = new TerminalLineFn(minusPrompt, equalsPrompt, lineReader); final SubShell subShell = diff --git a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java index 52abea95..6a30e9b3 100644 --- a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java +++ b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java @@ -176,7 +176,7 @@ Code toRel4(Environment env, Code code, Type type) { @Override protected CalciteFunctions.Context createContext( Environment env) { - final Session dummySession = new Session(); + final Session dummySession = new Session(ImmutableMap.of()); return new CalciteFunctions.Context(dummySession, env, typeSystem, calcite.dataContext.getTypeFactory()); } @@ -308,7 +308,7 @@ Code toRel4(Environment env, Code code, Type type) { new RelJson(jsonBuilder).toJson(rowType)); final String morelCode = apply.toString(); ThreadLocals.let(CalciteFunctions.THREAD_ENV, - new CalciteFunctions.Context(new Session(), cx.env, + new CalciteFunctions.Context(new Session(ImmutableMap.of()), cx.env, typeSystem, cx.relBuilder.getTypeFactory()), () -> cx.relBuilder.functionScan(CalciteFunctions.TABLE_OPERATOR, 0, cx.relBuilder.literal(morelCode), diff --git a/src/main/java/net/hydromatic/morel/compile/Compiler.java b/src/main/java/net/hydromatic/morel/compile/Compiler.java index 89d885b5..3f1ae2f0 100644 --- a/src/main/java/net/hydromatic/morel/compile/Compiler.java +++ b/src/main/java/net/hydromatic/morel/compile/Compiler.java @@ -47,6 +47,7 @@ import net.hydromatic.morel.util.ThreadLocals; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import org.apache.calcite.util.Util; @@ -133,7 +134,7 @@ public void eval(Session session, Environment env, * not created a session yet. Lifecycle confusion. * */ protected CalciteFunctions.Context createContext(Environment env) { - final Session dummySession = new Session(); + final Session dummySession = new Session(ImmutableMap.of()); return new CalciteFunctions.Context(dummySession, env, typeSystem, null); } diff --git a/src/main/java/net/hydromatic/morel/eval/Prop.java b/src/main/java/net/hydromatic/morel/eval/Prop.java index f2f87151..c0d0ff1b 100644 --- a/src/main/java/net/hydromatic/morel/eval/Prop.java +++ b/src/main/java/net/hydromatic/morel/eval/Prop.java @@ -21,6 +21,7 @@ import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableMap; +import java.io.File; import java.util.LinkedHashMap; import java.util.Map; @@ -29,6 +30,15 @@ * @see Session#map */ public enum Prop { + /** File property "directory" is the path of the directory that the + * {@code file} variable maps to in this connection. + * + *

The default value is the empty string; + * many tests use the "src/test/resources" directory; + * when launched via the {@code morel} shell script, the default value is the + * shell's current directory. */ + DIRECTORY("directory", File.class, new File("")), + /** Boolean property "hybrid" controls whether to try to create a hybrid * execution plan that uses Apache Calcite relational algebra wherever * possible. Default is false. */ @@ -114,6 +124,10 @@ public enum Prop { assert defaultValue == null || defaultValue.getClass() == type; } else if (type == Integer.class) { assert defaultValue == null || defaultValue.getClass() == type; + } else if (type == String.class) { + assert defaultValue == null || defaultValue.getClass() == type; + } else if (type == File.class) { + assert defaultValue == null || defaultValue.getClass() == type; } else { throw new AssertionError("not a valid property type: " + type); @@ -149,6 +163,20 @@ public int intValue(Map map) { return this.typeValue(o); } + /** Returns the value of a string property. */ + public String stringValue(Map map) { + assert type == String.class; + Object o = map.get(this); + return this.typeValue(o); + } + + /** Returns the value of a file property. */ + public File fileValue(Map map) { + assert type == File.class; + Object o = map.get(this); + return this.typeValue(o); + } + @SuppressWarnings("unchecked") private T typeValue(Object o) { if (o == null) { diff --git a/src/main/java/net/hydromatic/morel/eval/Session.java b/src/main/java/net/hydromatic/morel/eval/Session.java index 7f0d84bf..7e327596 100644 --- a/src/main/java/net/hydromatic/morel/eval/Session.java +++ b/src/main/java/net/hydromatic/morel/eval/Session.java @@ -39,11 +39,23 @@ public class Session { /** The output lines of the previous command. */ public List out; /** Property values. */ - public final Map map = new LinkedHashMap<>(); + public final Map map; /** Implementation of "use". */ private Shell shell = Shells.INSTANCE; + /** Creates a Session. + * + *

The {@code map} parameter, that becomes the property map, is used as is, + * not copied. It may be immutable if the session is for a narrow, internal + * use. Otherwise, it should probably be a {@link LinkedHashMap} to provide + * deterministic iteration order. + * + * @param map Map that contains property values */ + public Session(Map map) { + this.map = map; + } + /** Calls some code with a new value of {@link Shell}. */ public void withShell(Shell shell, Consumer outLines, Consumer consumer) { diff --git a/src/test/java/net/hydromatic/morel/Ml.java b/src/test/java/net/hydromatic/morel/Ml.java index 8d2e1ff2..f052d55b 100644 --- a/src/test/java/net/hydromatic/morel/Ml.java +++ b/src/test/java/net/hydromatic/morel/Ml.java @@ -267,7 +267,7 @@ Ml withPrepare(Consumer action) { final TypeSystem typeSystem = new TypeSystem(); final AstNode statement = parser.statementEof(); final Environment env = Environments.empty(); - final Session session = new Session(); + final Session session = new Session(propMap); final List warningList = new ArrayList<>(); final CompiledStatement compiled = Compiles.prepareStatement(typeSystem, session, env, statement, @@ -428,8 +428,7 @@ Ml assertEval(Matcher resultMatcher) { Ml assertEval() { return withValidate((resolved, calcite) -> { - final Session session = new Session(); - session.map.putAll(propMap); + final Session session = new Session(propMap); eval(session, resolved.env, resolved.typeMap.typeSystem, resolved.node, calcite); });