Generate a query string given an expression

Initialize share record and improve tests.
This commit is contained in:
eikek 2021-10-01 01:42:20 +02:00
parent d065abf7ac
commit de1baf725f
10 changed files with 718 additions and 16 deletions
modules
query/shared/src
store/src/main/scala/docspell/store

@ -123,9 +123,11 @@ object ItemQuery {
final case class ChecksumMatch(checksum: String) extends Expr
final case class AttachId(id: String) extends Expr
final case object ValidItemStates extends Expr
final case object Trashed extends Expr
final case object ValidItemsOrTrashed extends Expr
/** A "private" expression is only visible in code, but cannot be parsed. */
sealed trait PrivateExpr extends Expr
final case object ValidItemStates extends PrivateExpr
final case object Trashed extends PrivateExpr
final case object ValidItemsOrTrashed extends PrivateExpr
// things that can be expressed with terms above
sealed trait MacroExpr extends Expr {

@ -8,12 +8,23 @@ package docspell.query
import cats.data.NonEmptyList
import docspell.query.internal.ExprParser
import docspell.query.internal.ExprUtil
import docspell.query.internal.{ExprParser, ExprString, ExprUtil}
object ItemQueryParser {
val PrivateExprError = ExprString.PrivateExprError
type PrivateExprError = ExprString.PrivateExprError
def parse(input: String): Either[ParseFailure, ItemQuery] =
parse0(input, expandMacros = true)
def parseKeepMacros(input: String): Either[ParseFailure, ItemQuery] =
parse0(input, expandMacros = false)
private def parse0(
input: String,
expandMacros: Boolean
): Either[ParseFailure, ItemQuery] =
if (input.isEmpty)
Left(
ParseFailure("", 0, NonEmptyList.of(ParseFailure.SimpleMessage(0, "No input.")))
@ -24,9 +35,16 @@ object ItemQueryParser {
.parseQuery(in)
.left
.map(ParseFailure.fromError(in))
.map(q => q.copy(expr = ExprUtil.reduce(q.expr)))
.map(q => q.copy(expr = ExprUtil.reduce(expandMacros)(q.expr)))
}
def parseUnsafe(input: String): ItemQuery =
parse(input).fold(m => sys.error(m.render), identity)
def asString(q: ItemQuery.Expr): Either[PrivateExprError, String] =
ExprString(q)
def unsafeAsString(q: ItemQuery.Expr): String =
asString(q).fold(f => sys.error(s"Cannot expose private query part: $f"), identity)
}

@ -24,7 +24,7 @@ object BasicParser {
)
private[this] val identChars: Set[Char] =
(('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ "-_.").toSet
(('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ "-_.@").toSet
val parenAnd: P[Unit] =
P.stringIn(List("(&", "(and")).void <* ws0

@ -0,0 +1,244 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.query.internal
import java.time.Period
import docspell.query.Date
import docspell.query.Date.DateLiteral
import docspell.query.ItemQuery.Attr._
import docspell.query.ItemQuery.Expr._
import docspell.query.ItemQuery._
import docspell.query.internal.{Constants => C}
/** Creates the string representation for a given expression. The returned string can be
* parsed back to the expression using `ExprParser`. Note that expressions obtained from
* the `ItemQueryParser` have macros already expanded.
*
* It may fail when the expression contains non-public parts. Every expression that has
* been created by parsing a string, can be transformed back to a string. But an
* expression created via code may contain parts that cannot be transformed to a string.
*/
object ExprString {
final case class PrivateExprError(expr: Expr.PrivateExpr)
type Result = Either[PrivateExprError, String]
def apply(expr: Expr): Result =
expr match {
case AndExpr(inner) =>
val es = inner.traverse(ExprString.apply)
es.map(_.toList.mkString(" ")).map(els => s"(& $els )")
case OrExpr(inner) =>
val es = inner.traverse(ExprString.apply)
es.map(_.toList.mkString(" ")).map(els => s"(| $els )")
case NotExpr(inner) =>
inner match {
case NotExpr(inner2) =>
apply(inner2)
case _ =>
apply(inner).map(n => s"!$n")
}
case m: MacroExpr =>
Right(macroStr(m))
case DirectionExpr(v) =>
Right(s"${C.incoming}${C.like}${v}")
case InboxExpr(v) =>
Right(s"${C.inbox}${C.like}${v}")
case InExpr(attr, values) =>
val els = values.map(quote).toList.mkString(",")
Right(s"${attrStr(attr)}${C.in}$els")
case InDateExpr(attr, values) =>
val els = values.map(dateStr).toList.mkString(",")
Right(s"${attrStr(attr)}${C.in}$els")
case TagsMatch(op, values) =>
val els = values.map(quote).toList.mkString(",")
Right(s"${C.tag}${tagOpStr(op)}$els")
case TagIdsMatch(op, values) =>
val els = values.map(quote).toList.mkString(",")
Right(s"${C.tagId}${tagOpStr(op)}$els")
case Exists(field) =>
Right(s"${C.exist}${C.like}${attrStr(field)}")
case Fulltext(v) =>
Right(s"${C.content}${C.like}${quote(v)}")
case SimpleExpr(op, prop) =>
prop match {
case Property.StringProperty(attr, value) =>
Right(s"${stringAttr(attr)}${opStr(op)}${quote(value)}")
case Property.DateProperty(attr, value) =>
Right(s"${dateAttr(attr)}${opStr(op)}${dateStr(value)}")
case Property.IntProperty(attr, value) =>
Right(s"${attrStr(attr)}${opStr(op)}$value")
}
case TagCategoryMatch(op, values) =>
val els = values.map(quote).toList.mkString(",")
Right(s"${C.cat}${tagOpStr(op)}$els")
case CustomFieldMatch(name, op, value) =>
Right(s"${C.customField}:$name${opStr(op)}${quote(value)}")
case CustomFieldIdMatch(id, op, value) =>
Right(s"${C.customFieldId}:$id${opStr(op)}${quote(value)}")
case ChecksumMatch(cs) =>
Right(s"${C.checksum}${C.like}$cs")
case AttachId(aid) =>
Right(s"${C.attachId}${C.eqs}$aid")
case pe: PrivateExpr =>
// There is no parser equivalent for this
Left(PrivateExprError(pe))
}
private[internal] def macroStr(expr: Expr.MacroExpr): String =
expr match {
case Expr.NamesMacro(name) =>
s"${C.names}:${quote(name)}"
case Expr.YearMacro(_, year) =>
s"${C.year}:$year" //currently, only for Attr.Date
case Expr.ConcMacro(term) =>
s"${C.conc}:${quote(term)}"
case Expr.CorrMacro(term) =>
s"${C.corr}:${quote(term)}"
case Expr.DateRangeMacro(attr, left, right) =>
val name = attr match {
case Attr.CreatedDate =>
C.createdIn
case Attr.Date =>
C.dateIn
case Attr.DueDate =>
C.dueIn
}
(left, right) match {
case (_: Date.DateLiteral, Date.Calc(date, calc, period)) =>
s"$name:${dateStr(date)};${calcStr(calc)}${periodStr(period)}"
case (Date.Calc(date, calc, period), _: DateLiteral) =>
s"$name:${dateStr(date)};${calcStr(calc)}${periodStr(period)}"
case (Date.Calc(d1, _, p1), Date.Calc(_, _, _)) =>
s"$name:${dateStr(d1)};/${periodStr(p1)}"
case (_: DateLiteral, _: DateLiteral) =>
sys.error("Invalid date range")
}
}
private[internal] def dateStr(date: Date): String =
date match {
case Date.Today =>
"today"
case Date.Local(ld) =>
f"${ld.getYear}-${ld.getMonthValue}%02d-${ld.getDayOfMonth}%02d"
case Date.Millis(ms) =>
s"ms$ms"
case Date.Calc(date, calc, period) =>
val ds = dateStr(date)
s"$ds;${calcStr(calc)}${periodStr(period)}"
}
private[internal] def calcStr(c: Date.CalcDirection): String =
c match {
case Date.CalcDirection.Plus => "+"
case Date.CalcDirection.Minus => "-"
}
private[internal] def periodStr(p: Period): String =
if (p.toTotalMonths == 0) s"${p.getDays}d"
else s"${p.toTotalMonths}m"
private[internal] def attrStr(attr: Attr): String =
attr match {
case a: StringAttr => stringAttr(a)
case a: DateAttr => dateAttr(a)
case a: IntAttr => intAttr(a)
}
private[internal] def intAttr(attr: IntAttr): String =
attr match {
case AttachCount =>
Constants.attachCount
}
private[internal] def dateAttr(attr: DateAttr): String =
attr match {
case Attr.Date =>
Constants.date
case DueDate =>
Constants.due
case CreatedDate =>
Constants.created
}
private[internal] def stringAttr(attr: StringAttr): String =
attr match {
case Attr.ItemName =>
Constants.name
case Attr.ItemId =>
Constants.id
case Attr.ItemSource =>
Constants.source
case Attr.ItemNotes =>
Constants.notes
case Correspondent.OrgId =>
Constants.corrOrgId
case Correspondent.OrgName =>
Constants.corrOrgName
case Correspondent.PersonId =>
Constants.corrPersId
case Correspondent.PersonName =>
Constants.corrPersName
case Concerning.EquipId =>
Constants.concEquipId
case Concerning.EquipName =>
Constants.concEquipName
case Concerning.PersonId =>
Constants.concPersId
case Concerning.PersonName =>
Constants.concPersName
case Folder.FolderName =>
Constants.folder
case Folder.FolderId =>
Constants.folderId
}
private[internal] def opStr(op: Operator): String =
op match {
case Operator.Like => Constants.like.toString
case Operator.Gte => Constants.gte
case Operator.Lte => Constants.lte
case Operator.Eq => Constants.eqs.toString
case Operator.Lt => Constants.lt.toString
case Operator.Gt => Constants.gt.toString
case Operator.Neq => Constants.neq
}
private[internal] def tagOpStr(op: TagOperator): String =
op match {
case TagOperator.AllMatch => C.eqs.toString
case TagOperator.AnyMatch => C.like.toString
}
private def quote(s: String): String =
s"\"$s\""
}

@ -13,35 +13,42 @@ import docspell.query.ItemQuery._
object ExprUtil {
def reduce(expr: Expr): Expr =
reduce(expandMacros = true)(expr)
/** Does some basic transformation, like unfolding nested and trees containing one value
* etc.
*/
def reduce(expr: Expr): Expr =
def reduce(expandMacros: Boolean)(expr: Expr): Expr =
expr match {
case AndExpr(inner) =>
val nodes = spliceAnd(inner)
if (nodes.tail.isEmpty) reduce(nodes.head)
else AndExpr(nodes.map(reduce))
if (nodes.tail.isEmpty) reduce(expandMacros)(nodes.head)
else AndExpr(nodes.map(reduce(expandMacros)))
case OrExpr(inner) =>
val nodes = spliceOr(inner)
if (nodes.tail.isEmpty) reduce(nodes.head)
else OrExpr(nodes.map(reduce))
if (nodes.tail.isEmpty) reduce(expandMacros)(nodes.head)
else OrExpr(nodes.map(reduce(expandMacros)))
case NotExpr(inner) =>
inner match {
case NotExpr(inner2) =>
reduce(inner2)
reduce(expandMacros)(inner2)
case InboxExpr(flag) =>
InboxExpr(!flag)
case DirectionExpr(flag) =>
DirectionExpr(!flag)
case _ =>
NotExpr(reduce(inner))
NotExpr(reduce(expandMacros)(inner))
}
case m: MacroExpr =>
reduce(m.body)
if (expandMacros) {
reduce(expandMacros)(m.body)
} else {
m
}
case DirectionExpr(_) =>
expr

@ -0,0 +1,287 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.query
import java.time.{Instant, Period, ZoneOffset}
import cats.data.NonEmptyList
import docspell.query.ItemQuery.Expr.TagIdsMatch
import docspell.query.ItemQuery._
import org.scalacheck.Gen
/** Generator for syntactically valid queries. */
object ItemQueryGen {
def exprGen: Gen[Expr] =
Gen.oneOf(
simpleExprGen,
existsExprGen,
inExprGen,
inDateExprGen,
inboxExprGen,
directionExprGen,
tagIdsMatchExprGen,
tagMatchExprGen,
tagCatMatchExpr,
customFieldMatchExprGen,
customFieldIdMatchExprGen,
fulltextExprGen,
checksumMatchExprGen,
attachIdExprGen,
namesMacroGen,
corrMacroGen,
concMacroGen,
yearMacroGen,
dateRangeMacro,
Gen.lzy(andExprGen(exprGen)),
Gen.lzy(orExprGen(exprGen)),
Gen.lzy(notExprGen(exprGen))
)
def andExprGen(g: Gen[Expr]): Gen[Expr.AndExpr] =
nelGen(g).map(Expr.AndExpr)
def orExprGen(g: Gen[Expr]): Gen[Expr.OrExpr] =
nelGen(g).map(Expr.OrExpr)
// avoid generating nested not expressions, they are already flattened by the parser
// and only occur artificially
def notExprGen(g: Gen[Expr]): Gen[Expr] =
g.map {
case Expr.NotExpr(inner) => inner
case e => Expr.NotExpr(e)
}
val opGen: Gen[Operator] =
Gen.oneOf(
Operator.Like,
Operator.Gte,
Operator.Lt,
Operator.Gt,
Operator.Lte,
Operator.Eq,
Operator.Neq
)
val tagOpGen: Gen[TagOperator] =
Gen.oneOf(TagOperator.AllMatch, TagOperator.AnyMatch)
val stringAttrGen: Gen[Attr.StringAttr] =
Gen.oneOf(
Attr.Concerning.EquipName,
Attr.Concerning.EquipId,
Attr.Concerning.PersonName,
Attr.Concerning.PersonId,
Attr.Correspondent.OrgName,
Attr.Correspondent.OrgId,
Attr.Correspondent.PersonName,
Attr.Correspondent.PersonId,
Attr.ItemId,
Attr.ItemName,
Attr.ItemSource,
Attr.ItemNotes,
Attr.Folder.FolderId,
Attr.Folder.FolderName
)
val dateAttrGen: Gen[Attr.DateAttr] =
Gen.oneOf(Attr.Date, Attr.DueDate, Attr.CreatedDate)
val intAttrGen: Gen[Attr.IntAttr] =
Gen.const(Attr.AttachCount)
val attrGen: Gen[Attr] =
Gen.oneOf(stringAttrGen, dateAttrGen, intAttrGen)
private val valueChars =
Gen.oneOf(Gen.alphaNumChar, Gen.oneOf(" /{}*?-:@#$~+%…_[]^!ß"))
private val stringValueGen: Gen[String] =
Gen.choose(1, 20).flatMap(n => Gen.stringOfN(n, valueChars))
private val intValueGen: Gen[Int] =
Gen.choose(1900, 9999)
private val identGen: Gen[String] =
Gen
.choose(3, 12)
.flatMap(n =>
Gen.stringOfN(
n,
Gen.oneOf((('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ "-_.@").toSet)
)
)
private def nelGen[T](gen: Gen[T]): Gen[NonEmptyList[T]] =
for {
head <- gen
tail <- Gen.choose(0, 9).flatMap(n => Gen.listOfN(n, gen))
} yield NonEmptyList(head, tail)
private val dateMillisGen: Gen[Long] =
Gen.choose(0, Instant.parse("2100-12-24T20:00:00Z").toEpochMilli)
val localDateGen: Gen[Date.Local] =
dateMillisGen
.map(ms => Instant.ofEpochMilli(ms).atOffset(ZoneOffset.UTC).toLocalDate)
.map(Date.Local)
val millisDateGen: Gen[Date.Millis] =
dateMillisGen.map(Date.Millis)
val dateLiteralGen: Gen[Date.DateLiteral] =
Gen.oneOf(
localDateGen,
millisDateGen,
Gen.const(Date.Today)
)
val periodGen: Gen[Period] =
for {
mOrD <- Gen.oneOf(a => Period.ofDays(a), a => Period.ofMonths(a))
num <- Gen.choose(1, 30)
} yield mOrD(num)
val calcGen: Gen[Date.CalcDirection] =
Gen.oneOf(Date.CalcDirection.Plus, Date.CalcDirection.Minus)
val dateCalcGen: Gen[Date.Calc] =
for {
dl <- dateLiteralGen
calc <- calcGen
period <- periodGen
} yield Date.Calc(dl, calc, period)
val dateValueGen: Gen[Date] =
Gen.oneOf(dateLiteralGen, dateCalcGen)
val stringPropGen: Gen[Property.StringProperty] =
for {
attr <- stringAttrGen
sval <- stringValueGen
} yield Property.StringProperty(attr, sval)
val intPropGen: Gen[Property.IntProperty] =
for {
attr <- intAttrGen
ival <- intValueGen
} yield Property.IntProperty(attr, ival)
val datePropGen: Gen[Property.DateProperty] =
for {
attr <- dateAttrGen
dv <- dateValueGen
} yield Property.DateProperty(attr, dv)
val propertyGen: Gen[Property] =
Gen.oneOf(stringPropGen, datePropGen, intPropGen)
val simpleExprGen: Gen[Expr.SimpleExpr] =
for {
op <- opGen
prop <- propertyGen
} yield Expr.SimpleExpr(op, prop)
val existsExprGen: Gen[Expr.Exists] =
attrGen.map(Expr.Exists)
val inExprGen: Gen[Expr.InExpr] =
for {
attr <- stringAttrGen
vals <- nelGen(stringValueGen)
} yield Expr.InExpr(attr, vals)
val inDateExprGen: Gen[Expr.InDateExpr] =
for {
attr <- dateAttrGen
vals <- nelGen(dateValueGen)
} yield Expr.InDateExpr(attr, vals)
val inboxExprGen: Gen[Expr.InboxExpr] =
Gen.oneOf(true, false).map(Expr.InboxExpr)
val directionExprGen: Gen[Expr.DirectionExpr] =
Gen.oneOf(true, false).map(Expr.DirectionExpr)
val tagIdsMatchExprGen: Gen[Expr.TagIdsMatch] =
for {
op <- tagOpGen
vals <- nelGen(stringValueGen)
} yield TagIdsMatch(op, vals)
val tagMatchExprGen: Gen[Expr.TagsMatch] =
for {
op <- tagOpGen
vals <- nelGen(stringValueGen)
} yield Expr.TagsMatch(op, vals)
val tagCatMatchExpr: Gen[Expr.TagCategoryMatch] =
for {
op <- tagOpGen
vals <- nelGen(stringValueGen)
} yield Expr.TagCategoryMatch(op, vals)
val customFieldMatchExprGen: Gen[Expr.CustomFieldMatch] =
for {
name <- identGen
op <- opGen
value <- stringValueGen
} yield Expr.CustomFieldMatch(name, op, value)
val customFieldIdMatchExprGen: Gen[Expr.CustomFieldIdMatch] =
for {
name <- identGen
op <- opGen
value <- identGen
} yield Expr.CustomFieldIdMatch(name, op, value)
val fulltextExprGen: Gen[Expr.Fulltext] =
Gen
.choose(3, 20)
.flatMap(n => Gen.stringOfN(n, valueChars))
.map(Expr.Fulltext)
val checksumMatchExprGen: Gen[Expr.ChecksumMatch] =
Gen.stringOfN(64, Gen.hexChar).map(Expr.ChecksumMatch)
val attachIdExprGen: Gen[Expr.AttachId] =
identGen.map(Expr.AttachId)
val namesMacroGen: Gen[Expr.NamesMacro] =
stringValueGen.map(Expr.NamesMacro)
val concMacroGen: Gen[Expr.ConcMacro] =
stringValueGen.map(Expr.ConcMacro)
val corrMacroGen: Gen[Expr.CorrMacro] =
stringValueGen.map(Expr.CorrMacro)
val yearMacroGen: Gen[Expr.YearMacro] =
Gen.choose(1900, 9999).map(Expr.YearMacro(Attr.Date, _))
val dateRangeMacro: Gen[Expr.DateRangeMacro] =
for {
attr <- dateAttrGen
dl <- dateLiteralGen
p <- periodGen
calc <- Gen.option(calcGen)
range = calc match {
case Some(c @ Date.CalcDirection.Plus) =>
Expr.DateRangeMacro(attr, dl, Date.Calc(dl, c, p))
case Some(c @ Date.CalcDirection.Minus) =>
Expr.DateRangeMacro(attr, Date.Calc(dl, c, p), dl)
case None =>
Expr.DateRangeMacro(
attr,
Date.Calc(dl, Date.CalcDirection.Minus, p),
Date.Calc(dl, Date.CalcDirection.Plus, p)
)
}
} yield range
}

@ -0,0 +1,69 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.query.internal
import java.time.{LocalDate, Period}
import docspell.query.ItemQuery._
import docspell.query.{Date, ItemQueryGen, ParseFailure}
import munit.{FunSuite, ScalaCheckSuite}
import org.scalacheck.Prop.forAll
class ExprStringTest extends FunSuite with ScalaCheckSuite {
// parses the query without reducing and expanding macros
def singleParse(s: String): Expr =
ExprParser
.parseQuery(s)
.left
.map(ParseFailure.fromError(s))
.fold(f => sys.error(f.render), _.expr)
def exprString(expr: Expr): String =
ExprString(expr).fold(f => sys.error(f.toString), identity)
test("macro: name") {
val str = exprString(Expr.NamesMacro("test"))
val q = singleParse(str)
assertEquals(str, "names:\"test\"")
assertEquals(q, Expr.NamesMacro("test"))
}
test("macro: year") {
val str = exprString(Expr.YearMacro(Attr.Date, 1990))
val q = singleParse(str)
assertEquals(str, "year:1990")
assertEquals(q, Expr.YearMacro(Attr.Date, 1990))
}
test("macro: daterange") {
val range = Expr.DateRangeMacro(
attr = Attr.Date,
left = Date.Calc(
date = Date.Local(
date = LocalDate.of(2076, 12, 9)
),
calc = Date.CalcDirection.Minus,
period = Period.ofMonths(27)
),
right = Date.Local(LocalDate.of(2076, 12, 9))
)
val str = exprString(range)
val q = singleParse(str)
assertEquals(str, "dateIn:2076-12-09;-27m")
assertEquals(q, range)
}
property("generate expr and parse it") {
forAll(ItemQueryGen.exprGen) { expr =>
val str = exprString(expr)
val q = singleParse(str)
assertEquals(q, expr)
}
}
}

@ -8,7 +8,7 @@ package docspell.query.internal
import cats.implicits._
import docspell.query.ItemQueryParser
import docspell.query.{ItemQuery, ItemQueryParser}
import munit._
@ -64,4 +64,14 @@ class ItemQueryParserTest extends FunSuite {
ItemQueryParser.parseUnsafe("(| name:hello date:2021-02 name:world name:hello )")
assertEquals(expect.copy(raw = raw.some), q)
}
test("f.id:name=value") {
val raw = "f.id:QsuGW@=\"dAHBstXJd0\""
val q = ItemQueryParser.parseUnsafe(raw)
val expect =
ItemQuery.Expr.CustomFieldIdMatch("QsuGW@", ItemQuery.Operator.Eq, "dAHBstXJd0")
assertEquals(q.expr, expect)
}
}

@ -11,6 +11,7 @@ import java.time.{Instant, LocalDate}
import docspell.common._
import docspell.common.syntax.all._
import docspell.query.{ItemQuery, ItemQueryParser}
import docspell.totp.Key
import com.github.eikek.calev.CalEvent
@ -142,6 +143,11 @@ trait DoobieMeta extends EmilDoobieMeta {
implicit val metaByteSize: Meta[ByteSize] =
Meta[Long].timap(ByteSize.apply)(_.bytes)
implicit val metaItemQuery: Meta[ItemQuery] =
Meta[String].timap(s => ItemQueryParser.parseUnsafe(s))(q =>
q.raw.getOrElse(ItemQueryParser.unsafeAsString(q.expr))
)
}
object DoobieMeta extends DoobieMeta {

@ -0,0 +1,59 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.store.records
import cats.data.NonEmptyList
import docspell.common._
import docspell.query.ItemQuery
import docspell.store.qb._
final case class RShare(
id: Ident,
cid: Ident,
query: ItemQuery,
enabled: Boolean,
password: Option[Password],
publishedAt: Timestamp,
publishedUntil: Timestamp,
views: Int,
lastAccess: Option[Timestamp]
) {}
object RShare {
final case class Table(alias: Option[String]) extends TableDef {
val tableName = "item_share";
val id = Column[Ident]("id", this)
val cid = Column[Ident]("cid", this)
val query = Column[ItemQuery]("query", this)
val enabled = Column[Boolean]("enabled", this)
val password = Column[Password]("password", this)
val publishedAt = Column[Timestamp]("published_at", this)
val publishedUntil = Column[Timestamp]("published_until", this)
val views = Column[Int]("views", this)
val lastAccess = Column[Timestamp]("last_access", this)
val all: NonEmptyList[Column[_]] =
NonEmptyList.of(
id,
cid,
query,
enabled,
password,
publishedAt,
publishedUntil,
views,
lastAccess
)
}
val T: Table = Table(None)
def as(alias: String): Table = Table(Some(alias))
}