Commit Graph

76 Commits

Author SHA1 Message Date
Par Winzell 5c52a84916 I can think of no reason why this was ever allowed to be null. 2011-02-10 17:21:06 +00:00
Michael Bayne b8fec6cab5 Fixed a problem with binding transformed values in updatePartial.
When we go to bind values in an update statement, we either have:

- field names and a pojo from which to extract them, in which case
  transformation happens naturally when we extract the current value from the
  pojo

- value expressions (like 1, or "bob") which we were previously just binding
  directly to the statement, which was wrong because they need to first be
  transformed in the case where the field in question has an @Transform
  annotation; now we get the field marshaller for the field in question and
  pass the raw value to it, so that it can do the necessary transformations in
  writeToStatement()

- other expressions (like LiteralExp("true") or or IntervalExp or anything else
  where the database is involved in computing the final value to be assigned to
  the column).

This last case still holds the potential for badness with regard to transformed
fields. We can't magically transform the value to be stored into a field if the
database is computing the ultimate value. Say, for a contrived example, that
you had a record where you stored an int column as a String for shits and
giggles:

public class MyRecord {
    @Transform(IntToStringTransformer.class)
    public int someValue;
}

someValue will be a string column in the database, owing to the
IntToStringTransformer converting the int to a String.

Now you come along and think to yourself, "Hey, I want to store the current
minute in my  'int' field." and you write something like:

updatePartial(MyRecord.class, MyRecord.SOME_VALUE,
    DateFuncs.minute(Exps.literal(new Timestamp(System.currentTimeMillis()))));

Well, that's going to end up trying to stuff an int-valued expression (computed
by the database) into a String field, and the shit will hit the fan.

I can't really think of a non-contrived situation where this is likely to bite
us in the ass, but I don't especially like grass covered pits like this lying
around, waiting for someone to unsuspectingly step into them.

I could fail if you try to update a transformed field with anything other than
a ValueExp (or the current value from a pojo), but that would prevent you from
doing something potentially useful and safe like copying one field to another,
which both use the same transformation.
2011-02-05 00:22:53 +00:00
Michael Bayne 6c9d372510 Removed a final annotation that I had slipped in for testing. 2011-02-04 18:14:45 +00:00
Michael Bayne 71d3577074 Ur doin it wrong. The transforming field marshaller needs to operate on F (the
non-persistent type), and transform in getFromSet/writeToStatement, rather than
operate on T (the persistent type) and transform in
getFromObject/writeToObject.

This makes the newly added test in TransformTest work, rather than fail, as it
did previously.
2011-02-04 18:14:15 +00:00
Michael Bayne ea676d5b9e Added Tuple2.toMap for those times when you really want a map from a to b
rather than a list of (a, b).
2011-01-31 17:50:18 +00:00
Ray Greenwell f4b25369ff Bugfix. 2011-01-18 21:52:47 +00:00
Ray Greenwell 8e3a690b79 Fixed one little thing that bothered me: null enum values could
safely be stored, but if rehydrated into a Set then an EnumSet would
be created and that can't store nulls.
So, check to see if the encoded String has any nulls in it and
have the builder create a HashSet instead.
2011-01-18 21:37:38 +00:00
Ray Greenwell b32c6753c1 Added EnumIterable, which can be used to transform an Iterable
containing Enums. It extends StringBase, so the enums are stored
similarly to how a Set<String> is stored, and not based on ordinal.
2011-01-17 22:20:25 +00:00
Charlie Groves 63816b421c Don't complain that indices created by hsqldb are stale 2011-01-05 20:24:29 +00:00
Michael Bayne 1b21df9994 Added Funcs.countStar() because it's such a common SQL operation. 2010-12-18 18:01:57 +00:00
Michael Bayne 5dc96f1ac0 We need to ascend or descend on a particular column. 2010-12-18 17:47:31 +00:00
Michael Bayne 2796b3992d Stop calling me Shirley. 2010-12-18 17:37:23 +00:00
Michael Bayne 5809a5e8c6 New deal: every time you call a builder method, a copy of the Query is created,
so that partially constructed queries can be reused without any special care
being taken by the caller. I also switched to the use of a functional list
internally to make this whole process more pleasant and efficient.
2010-12-18 17:32:31 +00:00
Par Winzell 0fd6f66181 This was very confused. Unique columns are automatically indexed. Thus we should certainly not create an explicit index when we encounter a unique column, as we were doing, and we will even go so far as to forbid an explicit index. 2010-12-15 03:25:40 +00:00
Par Winzell f81b338cd9 Check for stale indexes, too. Use our knowledge of full text indexes to better decide if a column or index is internal to Depot's or the DB's workings. Finally, allow com.samskivert.depot.verifyschema to be set, forcing a staleness check (which slows down initialization somewhat). 2010-12-15 02:58:55 +00:00
Michael Bayne 367c10cc52 Javadoc fixes. 2010-12-14 17:09:43 +00:00
Michael Bayne d0919f8239 Correctify the documentation for selectInto(). 2010-12-14 16:41:16 +00:00
Ray Greenwell 5455490fcf PECS.
Even if the columns are String, Integer, and Boolean; it's not unreasonable
to accept a Builder that consumes Object, Number, and Comparable.
2010-12-13 19:48:46 +00:00
Michael Bayne 5e10393463 Added a totally type-safe mechanism for selecting ad-hoc query results into
pojos which hold the results of the query. The caller can decide if they want
to use the magic of selectInto(), and be careful not to screw up the types and
order of their pojo fields, or they can boil up some plates and create a
builder for their pojo, which will be type-checked, soup to nuts.
2010-12-13 19:29:50 +00:00
Michael Bayne 90b04e68b8 Prevent confusion if someone uses selectCount() (which returns int) on a
grouped query. They should be using select(Funcs.count(Exps.literal("*")))
(which returns List<Integer>) in such cases.
2010-12-13 17:50:50 +00:00
Charlie Groves 881a36bdca Log processed and skipped classes to ant's verbose logger to give some visibility into what's
happening if run with -v.
2010-12-11 00:10:15 +00:00
Charlie Groves fa94063dc7 Missing @Overrides 2010-12-10 21:40:35 +00:00
Michael Bayne 4b59373559 Added a mechanism for selecting results into a simple result class that relies
only on a matching public constructor. This version is not type-safe, but I'll
also be adding an additional mechanism to do selections like this that uses a
type-safe builder approach, which comes at the cost of a healthy dose of
boilerplate code.
2010-12-10 21:12:57 +00:00
Michael Bayne dd6dec7b9a Handle arrays (potentially of nested types). Ray's got some crazy shit over in
PX land.
2010-12-10 04:50:26 +00:00
Michael Bayne b6088302d5 Added a bunch of tests for the various functions appearing in a projection.
This uncovered a lot of loose geese as far as the database was concerned, which
necessitated turning a bunch of functions which had specific type into
functions which have type Number.

In general, I made the Funcs/StringFuncs/MathFuncs factory methods provide an
expression typed Number and I left the underlying SQLExpression derivations
able to be more precisely typed. So if you know that your database does not
magically promote an integer column to long when calling sum() on it, you can
force a more specific type. But really, I don't see why you would go to the
trouble. You can just call intValue() on your result and get the int you were
looking for regardless of what the database thought it would be fun to provide.

I also fixed a few HSQLDB variants (average -> avg, round(a) -> round(a, 0),
etc.) and made notes about a few things that just plain don't work in HSQLDB.
Awesome! Fortunately they're things like log-base-10, rather than more critical
functionality.
2010-12-10 00:48:52 +00:00
Michael Bayne fc94473ec4 The beans were above the frank. 2010-12-10 00:35:23 +00:00
Michael Bayne f2842f87da QueryBuilder -> Query. 2010-12-09 23:09:24 +00:00
Michael Bayne a0a9acbf16 Query -> Fetcher because I want to next change QueryBuilder -> Query so that it
looks nicer when you keep partially constructed queries around.
2010-12-09 23:05:43 +00:00
Michael Bayne 1b2df7b583 Return null from a load() with no matches, rather than throwing a NoSuchElement
exception. I'm tempted to reinstate the NoSuchElement exception for a load()
that attempts to project fields from the selected rows, but I think it makes
more sense for a load() which aggregates over zero rows to return null than to
throw a NoSuchElement exception.
2010-12-09 22:55:49 +00:00
Charlie Groves d93db7a0f7 Specify the types on Tuple.newTuple to make the captures line up 2010-12-09 21:53:49 +00:00
Michael Bayne 84738f9c57 Roll everything on up to Tuple5. We boil the plates so that you don't have to! 2010-12-09 01:44:27 +00:00
Michael Bayne 780d25866c Holy plans coming together Batman, selecting computed expressions is working.
Some examples:

int max = _repo.from(TestRecord.class).load(Funcs.max(TestRecord.RECORD_ID));

List<Tuple2<String,Integer>> data = _repo.from(EnumKeyRecord.class).
    groupBy(EnumKeyRecord.NAME).ascending(EnumKeyRecord.NAME).
    select(EnumKeyRecord.NAME, Funcs.count(EnumKeyRecord.TYPE));

Still no caching integration, but the ratio of utility over boilerplate is
high.
2010-12-09 01:14:21 +00:00
Michael Bayne 800c566fc6 Use DepotTypes to keep track of the types in our query. That's it's whole
purpose in life.
2010-12-09 00:42:34 +00:00
Michael Bayne f629b267ba I'd like to be able to require that you at least can only call greatest and
least on something that was Comparable, but unfortunately, the numeric type's
only shared supertype is Number and that is not comparable.

If I require Number, then you can't do greatest(timestamp, timestamp), and if I
require Comparable, then you can't do greatest(int, double) because the
compiler can't unify those types to something that is Comparable.

I could require that you only compare apples with apples (i.e. greatest(int,
int) or greatest(double, double)), but databases will happily compare
greatest(int, double) and people are going to want to do that. I could
introduce some sort of numeric conversion expressions, but I think that's
getting too deeply into the realm of type safety for type safety's sake.
2010-12-09 00:21:44 +00:00
Michael Bayne b01eaef2d8 The type safety turtles now go all the way down. This tightens up a bit more
fast-and-looseness by propagating types through foo.eq(bar) expressions,
foo.lessThan(bar) expressions and the like.

This means a few things stopped working, like
StringFuncs.length(MyRecord.BYTE_ARRAY_FIELD), but now there's
Funcs.arrayLength() for that. Also Date functions are looser than I'd like
because there's no supertype between sql.Date and sql.Timestamp (to express a
date having type) and sql.Timestamp and sql.Time (to express a time having
type). So you can still do some foot shooting with those methods, but it's
hardly worth crying over since the foot shooting range was fully open for
business prior to this.

MSOY fixes coming presently. ProjectX fixes shortly thereafter.
2010-12-09 00:08:58 +00:00
Michael Bayne ce71fa01fa More use of Preconditions. 2010-12-08 20:24:36 +00:00
Michael Bayne 84758df725 Added support for joins when doing field selections:
List<Tuple2<Integer,EnumKeyRecord.Type>> jdata =
    _repo.from(TestRecord.class).
      join(TestRecord.NAME, EnumKeyRecord.NAME).
      select(TestRecord.RECORD_ID, EnumKeyRecord.TYPE);

This should eliminate the need for pesky one-off computed records when doing
ad-hoc joins.
2010-12-08 20:03:27 +00:00
Michael Bayne 0bfe3959fc Add support for extracting our column data by position rather than name. 2010-12-08 20:00:21 +00:00
Michael Bayne 963a9a0fc1 Guava makes defensive programming more pleasant. 2010-12-08 19:35:50 +00:00
Michael Bayne b42ec35b09 Make this into a proper value class. 2010-12-08 19:19:22 +00:00
Michael Bayne 730f4c79ae Make the creation of true and false literals less hacky. 2010-12-08 19:18:52 +00:00
Michael Bayne 5bdc7d6746 We want to use the class that owns the column expression rather than supplying
one that may be incorrect.
2010-12-08 19:10:45 +00:00
Michael Bayne ad9f068355 Prefer a different fork to stick in it, per Ray. 2010-12-08 18:28:41 +00:00
Michael Bayne f6a2ad1000 Exciting new additions:
1. ColumnExp is now parameterized on the type of the column it represents. This
enables item number two.

2. The beginnings of a very primitive implementation of individual field
selection, which looks like:

  List<Tuple2<Integer,String>> results = from(FooRecord.class).where(
    FooRecord.ID.in(someIds)).select(FooRecord.ID, FooRecord.NAME);

I don't advise using this new functionality just yet, because it's very
primitive and will probably break if you try to do anything fancy. Plus it
doesn't interact with the cache at all yet.

However, I wanted to get the typed ColumnExp bits in there so that I can
migrate all the existing projects over and get that painful business out of the
way. Note for the jumpy: migration in this case means eliminating a bunch of
unchecked type usage warnings, rather than behavioral changes. The existing
Depot functionality continues to work exactly as is. The types are not used
except for in the new individual field selection code.

One bit of code that will break is calls to updatePartial() which used to take
a Map<ColumnExp, ?> and now take a Map<ColumnExp<?>, ?>. Since those were
already in generics land, they won't just fail with an unchecked type warning,
the compiler will reject the old call (the calls are binary compatible, so
unrecompiled code will still work fine). Fortunately the Map-taking
updatePartial is not called that much outside of MSOY, which I have already
migrated to fully parameterized ColumnExp land.
2010-12-08 08:23:27 +00:00
Michael Bayne dd85f630e4 We need to preserve our type parameter. 2010-12-08 00:42:11 +00:00
Michael Bayne fc4cda9260 Allow multiple join clauses. Added a clone() method for reusing partially
configured queries.
2010-12-08 00:41:06 +00:00
Michael Bayne 355b084292 Further QueryBuilder improvements. In any case where you may want a specially
configured clause, allow adding the clause directly. Removed some repetition by
using said methods from factory versions. Other tidying.
2010-12-08 00:23:15 +00:00
Michael Bayne fca5cb2386 Added QueryBuilder.delete() along with a bunch of sanity checking. I considered
having a separate deleteFrom(record).where(...).delete() builder, but
from(record).where(...).delete() was so much nicer that I decided that
preventing the specification of invalid clauses could be deferred from compile
to runtime.

I could do some fiddly things with return types and have from() return a
builder that then subsequently pigeon-holed you into deletability or not
depending on whether you called things like orderBy(), but to do so in Java
would require that I override every method to specialize its return type, which
presents something of a maintenance annoyance.
2010-12-07 23:41:27 +00:00
Michael Bayne 1e86fba829 Added a limit that takes only a count. 2010-12-07 23:14:29 +00:00
Michael Bayne f48a688d99 Scratch the FluentRepository, let's do things properly with a QueryBuilder.
With this you can write queries like:

  MaxPointsRecord max = from(MaxPointsRecord.class).
      override(DailyScoreRecord.class),
      where(DailyScoreRecord.KEY.eq(key)).load();

  List<DailyLeaderRecord> top = from(DailyLeaderRecord.class).
      override(DailyScoreRecord.class).
      join(DailyScoreRecord.KNIGHT_ID, KnightRecord.KNIGHT_ID).
      where(DailyScoreRecord.KEY.eq(key),
            DailyScoreRecord.POINTS.eq(max.maxPoints)).
      select();

After configuring your query, you can load(), you can select(), you can
selectKeys() and you can selectCount(). Other improvements include configuring
cache behavior (from(FooRecord.class).noCache().where(...).select()).

random() became randomOrder() because I think that seeing random() stuffed in
the middle of a query is insufficiently communicative.

Next, I'm going to look into extending the query builder to allow things like:

   from(MemberRecord.class).
       where(MemberRecord.CREATED.greaterThan(date)).
       select(MemberRecord.AVATAR_ID, MemberRecord.HOME_SCENE_ID);
   // yields List<Tuple2<Integer, Integer>>

which will require adding types to ColumnExp, which I hope to do in a
moderately backwards compatible way. If I can, I may use those types to enforce
type correctness and turn things like the following into a compiler error:

   MemberRecord.MEMBER_ID.greaterThan("bob")

If I had a more sophisticated type system, I could even make an attempt to call
greaterThan() on a ColumnExp<String> a compilation error.

If the above doesn't turn out to be a rabbit hole extraordinairre (it probably
will), I'd like to look into obviating the need for most of the FieldOverride
and FromOverride fiddling, and support things like:

   from(MemberRecord.class).
       where(MemberRecord.CREATED.greaterThan(date)).
       select(Exps.max(MemberRecord.SESSIONS));
   // yields List<Integer>

which would take Depot in the direction of being a (somewhat) type safe wrapper
around SQL. This would go a long way toward bridging the conceptual gap between
"the SQL I know I want to write" and "how the fuck do I do this with this Java
API".

I don't think I'm going to be able to get too far with the type-safe wrapper
approach without propagating types all over the place, which may result in
backwards compatibility breakage. If things get really bad, I may just have to
pinch off the Depot 1.x series and venture into the green fields of Depot 2.x.
I'll see how far I can get before taking such drastic action, however.
2010-12-07 22:49:28 +00:00