diff --git a/basis/alarms/summary.txt b/basis/alarms/summary.txt
new file mode 100644
index 0000000000..f6e12238fa
--- /dev/null
+++ b/basis/alarms/summary.txt
@@ -0,0 +1 @@
+One-time and recurring events
diff --git a/basis/alias/summary.txt b/basis/alias/summary.txt
new file mode 100644
index 0000000000..15690a7b9b
--- /dev/null
+++ b/basis/alias/summary.txt
@@ -0,0 +1 @@
+Defining multiple words with the same name
diff --git a/basis/ascii/ascii-docs.factor b/basis/ascii/ascii-docs.factor
index 75af8a7102..6af697cf89 100755
--- a/basis/ascii/ascii-docs.factor
+++ b/basis/ascii/ascii-docs.factor
@@ -46,6 +46,6 @@ ARTICLE: "ascii" "ASCII character classes"
{ $subsection printable? }
{ $subsection control? }
{ $subsection quotable? }
-"Modern applications should use Unicode 5.0 instead (" { $vocab-link "unicode" } ")." ;
+"Modern applications should use Unicode 5.0 instead (" { $vocab-link "unicode.categories" } ")." ;
ABOUT: "ascii"
diff --git a/basis/binary-search/summary.txt b/basis/binary-search/summary.txt
new file mode 100644
index 0000000000..c4fd4f2774
--- /dev/null
+++ b/basis/binary-search/summary.txt
@@ -0,0 +1 @@
+Fast searching of sorted arrays
diff --git a/basis/bootstrap/image/image.factor b/basis/bootstrap/image/image.factor
index 9284728a7a..db8e8c8ec0 100755
--- a/basis/bootstrap/image/image.factor
+++ b/basis/bootstrap/image/image.factor
@@ -26,7 +26,6 @@ IN: bootstrap.image
"x86.32"
"x86.64"
"linux-ppc" "macosx-ppc"
- ! "arm"
} ;
assoc
+ {
+ class<=-cache class-not-cache classes-intersect-cache
+ class-and-cache class-or-cache next-method-quot-cache
+ } [ H{ } clone ] H{ } map>assoc assoc-union
bootstrap-global set
bootstrap-global emit-userenv ;
diff --git a/basis/bootstrap/random/random.factor b/basis/bootstrap/random/random.factor
index 3782d517cf..f6527cdda1 100755
--- a/basis/bootstrap/random/random.factor
+++ b/basis/bootstrap/random/random.factor
@@ -13,4 +13,4 @@ IN: bootstrap.random
[
[ 32 random-bits ] with-system-random
random-generator set-global
-] "generator.random" add-init-hook
+] "bootstrap.random" add-init-hook
diff --git a/basis/boxes/summary.txt b/basis/boxes/summary.txt
new file mode 100644
index 0000000000..44c1352e36
--- /dev/null
+++ b/basis/boxes/summary.txt
@@ -0,0 +1 @@
+An abstraction for enforcing a mutual-exclusion invariant
diff --git a/basis/calendar/calendar-docs.factor b/basis/calendar/calendar-docs.factor
index 62ff4ad517..c3d84fc783 100644
--- a/basis/calendar/calendar-docs.factor
+++ b/basis/calendar/calendar-docs.factor
@@ -21,8 +21,8 @@ HELP:
{ $description "Returns a timestamp object representing the start of the specified day in your current timezone." }
{ $examples
{ $example "USING: calendar prettyprint ;"
- "2010 12 25 ."
- "T{ timestamp\n { year 2010 }\n { month 12 }\n { day 25 }\n { gmt-offset T{ duration { hour -5 } } }\n}"
+ "2010 12 25 >gmt midnight ."
+ "T{ timestamp { year 2010 } { month 12 } { day 25 } }"
}
} ;
diff --git a/basis/calendar/format/format-tests.factor b/basis/calendar/format/format-tests.factor
index c433a118c2..81930cdf49 100755
--- a/basis/calendar/format/format-tests.factor
+++ b/basis/calendar/format/format-tests.factor
@@ -62,3 +62,15 @@ IN: calendar.format.tests
T{ duration f 0 0 0 -5 0 0 }
}
] [ "2008-05-26T00:37:42.12345-05:00" rfc3339>timestamp ] unit-test
+
+[
+ T{ timestamp
+ { year 2008 }
+ { month 10 }
+ { day 2 }
+ { hour 23 }
+ { minute 59 }
+ { second 59 }
+ { gmt-offset T{ duration f 0 0 0 0 0 0 } }
+ }
+] [ "Thursday, 02-Oct-2008 23:59:59 GMT" cookie-string>timestamp ] unit-test
diff --git a/basis/calendar/format/format.factor b/basis/calendar/format/format.factor
index bfe438fae1..b15da42409 100755
--- a/basis/calendar/format/format.factor
+++ b/basis/calendar/format/format.factor
@@ -201,9 +201,13 @@ ERROR: invalid-timestamp-format ;
: rfc822>timestamp ( str -- timestamp )
[ (rfc822>timestamp) ] with-string-reader ;
+: check-day-name ( str -- )
+ [ day-abbreviations3 member? ] [ day-names member? ] bi or
+ check-timestamp drop ;
+
: (cookie-string>timestamp-1) ( -- timestamp )
timestamp new
- "," read-token day-abbreviations3 member? check-timestamp drop
+ "," read-token check-day-name
read1 CHAR: \s assert=
"-" read-token checked-number >>day
"-" read-token month-abbreviations index 1+ check-timestamp >>month
@@ -218,7 +222,7 @@ ERROR: invalid-timestamp-format ;
: (cookie-string>timestamp-2) ( -- timestamp )
timestamp new
- read-sp day-abbreviations3 member? check-timestamp drop
+ read-sp check-day-name
read-sp month-abbreviations index 1+ check-timestamp >>month
read-sp checked-number >>day
":" read-token checked-number >>hour
diff --git a/basis/channels/remote/tags.txt b/basis/channels/remote/tags.txt
index f4274299b1..ce745d18c6 100644
--- a/basis/channels/remote/tags.txt
+++ b/basis/channels/remote/tags.txt
@@ -1 +1 @@
-extensions
+concurrency
diff --git a/basis/channels/tags.txt b/basis/channels/tags.txt
index f4274299b1..ce745d18c6 100644
--- a/basis/channels/tags.txt
+++ b/basis/channels/tags.txt
@@ -1 +1 @@
-extensions
+concurrency
diff --git a/basis/circular/circular-docs.factor b/basis/circular/circular-docs.factor
index 362d41c9de..c7af57c1fe 100644
--- a/basis/circular/circular-docs.factor
+++ b/basis/circular/circular-docs.factor
@@ -43,7 +43,7 @@ HELP: push-growing-circular
{ "elt" object } { "circular" circular } }
{ $description "Pushes an element onto a " { $link growing-circular } " object." } ;
-ARTICLE: "circular" "circular"
+ARTICLE: "circular" "Circular sequences"
"The " { $vocab-link "circular" } " vocabulary implements the " { $link "sequence-protocol" } " to allow an arbitrary start index and wrap-around indexing." $nl
"Creating a new circular object:"
{ $subsection }
diff --git a/basis/cocoa/cocoa-docs.factor b/basis/cocoa/cocoa-docs.factor
index 01b0809f37..a971288251 100644
--- a/basis/cocoa/cocoa-docs.factor
+++ b/basis/cocoa/cocoa-docs.factor
@@ -19,7 +19,7 @@ HELP: SUPER->
ARTICLE: "objc-calling" "Calling Objective C code"
"Before an Objective C class can be used, it must be imported; by default, a small set of common classes are imported automatically, but additional classes can be imported as needed."
{ $subsection import-objc-class }
-"Every imported Objective C class has as corresponding class word in the " { $vocab-link "objc-classes" } " vocabulary. Class words push the class object in the stack, allowing class methods to be invoked."
+"Every imported Objective C class has as corresponding class word in the " { $vocab-link "cocoa.classes" } " vocabulary. Class words push the class object in the stack, allowing class methods to be invoked."
$nl
"Messages can be sent to classes and instances using a pair of parsing words:"
{ $subsection POSTPONE: -> }
diff --git a/basis/colors/summary.txt b/basis/colors/summary.txt
new file mode 100644
index 0000000000..a90b1aaf76
--- /dev/null
+++ b/basis/colors/summary.txt
@@ -0,0 +1 @@
+Colors as a first-class data type
diff --git a/basis/combinators/short-circuit/short-circuit-docs.factor b/basis/combinators/short-circuit/short-circuit-docs.factor
index 058291d022..54fc3aac43 100644
--- a/basis/combinators/short-circuit/short-circuit-docs.factor
+++ b/basis/combinators/short-circuit/short-circuit-docs.factor
@@ -64,7 +64,7 @@ HELP: n||-rewrite
{ "quot" quotation } }
{ $description "A macro that reqrites the code to pass " { $snippet "N" } " parameters from the stack to each OR quotation." } ;
-ARTICLE: "combinators.short-circuit" "combinators.short-circuit"
+ARTICLE: "combinators.short-circuit" "Short-circuit combinators"
"The " { $vocab-link "combinators.short-circuit" } " vocabulary stops a computation early once a condition is met." $nl
"AND combinators:"
{ $subsection 0&& }
diff --git a/basis/combinators/short-circuit/smart/smart-docs.factor b/basis/combinators/short-circuit/smart/smart-docs.factor
index abf3ff0eef..34abde15b6 100644
--- a/basis/combinators/short-circuit/smart/smart-docs.factor
+++ b/basis/combinators/short-circuit/smart/smart-docs.factor
@@ -27,8 +27,9 @@ HELP: ||
}
} ;
-ARTICLE: "combinators.short-circuit.smart" "combinators.short-circuit.smart"
-"The " { $vocab-link "combinators.short-circuit.smart" } " vocabulary infers the number of inputs that the sequence of quotations takes." $nl
+ARTICLE: "combinators.short-circuit.smart" "Smart short-circuit combinators"
+"The " { $vocab-link "combinators.short-circuit.smart" } " vocabulary is similar to " { $vocab-link "combinators.short-circuit" } " except the combinators here infer the number of inputs that the sequence of quotations takes."
+$nl
"Generalized AND:"
{ $subsection && }
"Generalized OR:"
diff --git a/basis/compiler/generator/generator-docs.factor b/basis/compiler/generator/generator-docs.factor
index 45238ab00a..5d485b13d4 100755
--- a/basis/compiler/generator/generator-docs.factor
+++ b/basis/compiler/generator/generator-docs.factor
@@ -4,7 +4,7 @@ kernel vectors arrays effects sequences ;
IN: compiler.generator
ARTICLE: "generator" "Compiled code generator"
-"Most of the words in the " { $vocab-link "generator" } " vocabulary are internal to the compiler and user code has no reason to call them."
+"Most of the words in the " { $vocab-link "compiler.generator" } " vocabulary are internal to the compiler and user code has no reason to call them."
$nl
"Debugging information can be enabled or disabled; this hook is used by " { $link "tools.deploy" } ":"
{ $subsection compiled-stack-traces? }
diff --git a/basis/compiler/intrinsics/intrinsics.factor b/basis/compiler/intrinsics/intrinsics.factor
index b995e6d737..471c05ee59 100644
--- a/basis/compiler/intrinsics/intrinsics.factor
+++ b/basis/compiler/intrinsics/intrinsics.factor
@@ -4,20 +4,42 @@ USING: kernel classes.tuple classes.tuple.private math arrays
byte-arrays words stack-checker.known-words ;
IN: compiler.intrinsics
-: (tuple) ( layout -- tuple )
- "BUG: missing (tuple) intrinsic" throw ;
+ERROR: missing-intrinsic ;
+
+: (tuple) ( n -- tuple ) missing-intrinsic ;
\ (tuple) { tuple-layout } { tuple } define-primitive
\ (tuple) make-flushable
-: (array) ( n -- array )
- "BUG: missing (array) intrinsic" throw ;
+: (array) ( n -- array ) missing-intrinsic ;
\ (array) { integer } { array } define-primitive
\ (array) make-flushable
-: (byte-array) ( n -- byte-array )
- "BUG: missing (byte-array) intrinsic" throw ;
+: (byte-array) ( n -- byte-array ) missing-intrinsic ;
\ (byte-array) { integer } { byte-array } define-primitive
\ (byte-array) make-flushable
+
+: (ratio) ( -- ratio ) missing-intrinsic ;
+
+\ (ratio) { } { ratio } define-primitive
+\ (ratio) make-flushable
+
+: (complex) ( -- complex ) missing-intrinsic ;
+
+\ (complex) { } { complex } define-primitive
+\ (complex) make-flushable
+
+: (wrapper) ( -- wrapper ) missing-intrinsic ;
+
+\ (wrapper) { } { wrapper } define-primitive
+\ (wrapper) make-flushable
+
+: (set-slot) ( val obj n -- ) missing-intrinsic ;
+
+\ (set-slot) { object object fixnum } { } define-primitive
+
+: (write-barrier) ( obj -- ) missing-intrinsic ;
+
+\ (write-barrier) { object } { } define-primitive
diff --git a/basis/compiler/tree/propagation/info/info.factor b/basis/compiler/tree/propagation/info/info.factor
index 0891a6629c..5f8de4eb49 100644
--- a/basis/compiler/tree/propagation/info/info.factor
+++ b/basis/compiler/tree/propagation/info/info.factor
@@ -298,6 +298,12 @@ SYMBOL: value-infos
: node-output-infos ( node -- seq )
dup out-d>> [ node-value-info ] with map ;
+: first-literal ( #call -- obj )
+ dup in-d>> first node-value-info literal>> ;
+
+: last-literal ( #call -- obj )
+ dup out-d>> peek node-value-info literal>> ;
+
: immutable-tuple-boa? ( #call -- ? )
dup word>> \ eq? [
dup in-d>> peek node-value-info
diff --git a/basis/concurrency/combinators/tags.txt b/basis/concurrency/combinators/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/combinators/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/conditions/tags.txt b/basis/concurrency/conditions/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/conditions/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/count-downs/tags.txt b/basis/concurrency/count-downs/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/count-downs/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/distributed/tags.txt b/basis/concurrency/distributed/tags.txt
index 50cfa263f6..b7861c6689 100644
--- a/basis/concurrency/distributed/tags.txt
+++ b/basis/concurrency/distributed/tags.txt
@@ -1,2 +1,2 @@
+concurrency
enterprise
-extensions
diff --git a/basis/concurrency/exchangers/tags.txt b/basis/concurrency/exchangers/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/exchangers/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/flags/tags.txt b/basis/concurrency/flags/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/flags/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/futures/tags.txt b/basis/concurrency/futures/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/futures/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/locks/tags.txt b/basis/concurrency/locks/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/locks/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/mailboxes/tags.txt b/basis/concurrency/mailboxes/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/mailboxes/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/messaging/tags.txt b/basis/concurrency/messaging/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/messaging/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/promises/tags.txt b/basis/concurrency/promises/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/promises/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/concurrency/semaphores/tags.txt b/basis/concurrency/semaphores/tags.txt
new file mode 100644
index 0000000000..ce745d18c6
--- /dev/null
+++ b/basis/concurrency/semaphores/tags.txt
@@ -0,0 +1 @@
+concurrency
diff --git a/basis/core-foundation/fsevents/fsevents.factor b/basis/core-foundation/fsevents/fsevents.factor
index bb21391f0a..6bec4b23c0 100644
--- a/basis/core-foundation/fsevents/fsevents.factor
+++ b/basis/core-foundation/fsevents/fsevents.factor
@@ -3,13 +3,10 @@
USING: alien alien.c-types alien.strings alien.syntax kernel
math sequences namespaces make assocs init accessors
continuations combinators core-foundation
-core-foundation.run-loop io.encodings.utf8 destructors ;
+core-foundation.run-loop core-foundation.run-loop.thread
+io.encodings.utf8 destructors ;
IN: core-foundation.fsevents
-! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
-! FSEventStream API, Leopard only !
-! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
-
: kFSEventStreamCreateFlagUseCFTypes 2 ; inline
: kFSEventStreamCreateFlagWatchRoot 4 ; inline
diff --git a/basis/core-foundation/run-loop/run-loop.factor b/basis/core-foundation/run-loop/run-loop.factor
index 5ffcafbbaf..e30cc2eb60 100644
--- a/basis/core-foundation/run-loop/run-loop.factor
+++ b/basis/core-foundation/run-loop/run-loop.factor
@@ -35,5 +35,3 @@ FUNCTION: SInt32 CFRunLoopRunInMode (
: start-run-loop-thread ( -- )
[ run-loop-thread t ] "CFRunLoop dispatcher" spawn-server drop ;
-
-[ start-run-loop-thread ] "core-foundation.run-loop" add-init-hook
diff --git a/basis/core-foundation/run-loop/thread/thread.factor b/basis/core-foundation/run-loop/thread/thread.factor
new file mode 100644
index 0000000000..326226ec0e
--- /dev/null
+++ b/basis/core-foundation/run-loop/thread/thread.factor
@@ -0,0 +1,8 @@
+! Copyright (C) 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
+USING: init core-foundation.run-loop ;
+IN: core-foundation.run-loop.thread
+
+! Load this vocabulary if you need a run loop running.
+
+[ start-run-loop-thread ] "core-foundation.run-loop.thread" add-init-hook
diff --git a/basis/db/db-docs.factor b/basis/db/db-docs.factor
index f8e3956b3e..74b72b8789 100644
--- a/basis/db/db-docs.factor
+++ b/basis/db/db-docs.factor
@@ -1,7 +1,7 @@
! Copyright (C) 2008 Doug Coleman.
! See http://factorcode.org/license.txt for BSD license.
USING: classes kernel help.markup help.syntax sequences
-alien assocs strings math multiline ;
+alien assocs strings math multiline quotations ;
IN: db
HELP: db
@@ -45,7 +45,22 @@ HELP: prepared-statement
{ $description } ;
HELP: result-set
-{ $description } ;
+{ $description "An object encapsulating a raw SQL result object. There are two ways in which a result set can be accessed, but they are specific to the database backend in use."
+ { $subsection "db-random-access-result-set" }
+ { $subsection "db-sequential-result-set" }
+} ;
+
+HELP: init-result-set
+{ $values
+ { "result-set" result-set } }
+{ $description "" } ;
+
+HELP: new-result-set
+{ $values
+ { "query" "a query" } { "handle" alien } { "class" class }
+ { "result-set" result-set } }
+{ $description "Creates a new " { $link result-set } " object of type " { $snippet "class" } "." } ;
+
HELP: new-statement
{ $values { "sql" string } { "in" sequence } { "out" sequence } { "class" class } { "statement" statement } }
@@ -81,7 +96,7 @@ HELP: query-results
{ $values { "query" object }
{ "result-set" result-set }
}
-{ $description "" } ;
+{ $description "Returns a " { $link result-set } " object representing the reults of a SQL query." } ;
HELP: #rows
{ $values { "result-set" result-set } { "n" integer } }
@@ -95,36 +110,126 @@ HELP: row-column
{ $values { "result-set" result-set } { "column" integer }
{ "obj" object }
}
-{ $description "" } ;
+{ $description "Returns the value indexed by " { $snippet "column" } " in the current row of a " { $link result-set } "." } ;
HELP: row-column-typed
{ $values { "result-set" result-set } { "column" integer }
{ "sql" "sql" } }
-{ $description "" } ;
+{ $description "Returns the value indexed by " { $snippet "column" } " in the current row of a " { $link result-set } " and converts the result based on a type stored in the " { $link result-set } "'s " { $slot "out-params" } "." } ;
HELP: advance-row
{ $values { "result-set" result-set } }
-;
+{ $description "Advanced the pointer to an underlying SQL result set stored in a " { $link result-set } " object." } ;
HELP: more-rows?
{ $values { "result-set" result-set } { "?" "a boolean" } }
-;
+{ $description "Returns true if the " { $link result-set } " has more rows to traverse." } ;
HELP: execute-statement*
{ $values { "statement" statement } { "type" object } }
{ $description } ;
+HELP: execute-one-statement
+{ $values
+ { "statement" null } }
+{ $description "" } ;
+
HELP: execute-statement
{ $values { "statement" statement } }
-{ $description } ;
+{ $description "" } ;
-ARTICLE: "db" "Low-level database library"
+
+
+
+
+
+HELP: begin-transaction
+{ $description "Begins a new transaction. User code should make use of the " { $link with-transaction } " combinator." } ;
+
+HELP: bind-statement
+{ $values
+ { "obj" object } { "statement" null } }
+{ $description "" } ;
+
+HELP: commit-transaction
+{ $description "Commits a transaction. User code should make use of the " { $link with-transaction } " combinator." } ;
+
+HELP: default-query
+{ $values
+ { "query" null }
+ { "result-set" null } }
+{ $description "" } ;
+
+HELP: in-transaction
+{ $description "A variable that is set true when a transaction is in progress." } ;
+
+HELP: in-transaction?
+{ $values
+ { "?" "a boolean" } }
+{ $description "Returns true if there is currently a transaction in progress in this scope." } ;
+
+HELP: query-each
+{ $values
+ { "statement" null } { "quot" quotation } }
+{ $description "" } ;
+
+HELP: query-map
+{ $values
+ { "statement" null } { "quot" quotation }
+ { "seq" sequence } }
+{ $description "" } ;
+
+HELP: rollback-transaction
+{ $description "Rolls back a transaction; no data is committed to the database. User code should make use of the " { $link with-transaction } " combinator." } ;
+
+HELP: sql-command
+{ $values
+ { "sql" string } }
+{ $description "Executes a SQL string using the databse in the " { $link db } " symbol." } ;
+
+HELP: sql-query
+{ $values
+ { "sql" string }
+ { "rows" "an array of arrays of strings" } }
+{ $description "Runs a SQL query of raw text in the database in the " { $link db } " symbol. Each row is returned as an array of strings; no type-conversions are done on the resulting data." } ;
+
+{ sql-command sql-query } related-words
+
+HELP: sql-row
+{ $values
+ { "result-set" result-set }
+ { "seq" sequence } }
+{ $description "Returns the current row in a " { $link result-set } " as an array of strings." } ;
+
+HELP: sql-row-typed
+{ $values
+ { "result-set" result-set }
+ { "seq" sequence } }
+{ $description "Returns the current row in a " { $link result-set } " as an array of typed Factor objects." } ;
+
+{ sql-row sql-row-typed } related-words
+
+HELP: with-db
+{ $values
+ { "seq" sequence } { "class" class } { "quot" quotation } }
+{ $description "Calls the quotation with a database bound to the " { $link db } " symbol. The database called is based on the " { $snippet "class" } " with the " } ;
+
+HELP: with-transaction
+{ $values
+ { "quot" quotation } }
+{ $description "" } ;
+
+ARTICLE: "db" "Database library"
{ $subsection "db-custom-database-combinators" }
{ $subsection "db-protocol" }
+{ $subsection "db-result-sets" }
{ $subsection "db-lowlevel-tutorial" }
"Higher-level database:"
{ $vocab-subsection "Database types" "db.types" }
{ $vocab-subsection "High-level tuple/database integration" "db.tuples" }
+! { $subsection "db-tuples" }
+! { $subsection "db-tuples-protocol" }
+! { $subsection "db-tuples-tutorial" }
"Supported database backends:"
{ $vocab-subsection "SQLite" "db.sqlite" }
{ $vocab-subsection "PostgreSQL" "db.postgresql" }
@@ -132,6 +237,40 @@ ARTICLE: "db" "Low-level database library"
{ $subsection "db-porting-the-library" }
;
+ARTICLE: "db-random-access-result-set" "Random access result sets"
+"Random-access result sets do not have to be traversed in order. For instance, PostgreSQL's result set object can be accessed as a matrix with i,j coordinates."
+$nl
+"Databases which work in this way must provide methods for the following traversal words:"
+{ $subsection #rows }
+{ $subsection #columns }
+{ $subsection row-column }
+{ $subsection row-column-typed } ;
+
+ARTICLE: "db-sequential-result-set" "Sequential result sets"
+"Sequential result sets can be iterated one element after the next. SQLite's result sets offer this method of traversal."
+$nl
+"Databases which work in this way must provide methods for the following traversal words:"
+{ $subsection more-rows? }
+{ $subsection advance-row }
+{ $subsection row-column }
+{ $subsection row-column-typed } ;
+
+ARTICLE: "db-result-sets" "Result sets"
+"Result sets are the encapsulated, database-specific results from a SQL query."
+$nl
+"Two possible protocols for iterating over result sets exist:"
+{ $subsection "db-random-access-result-set" }
+{ $subsection "db-sequential-result-set" }
+"Query the number of rows or columns:"
+{ $subsection #rows }
+{ $subsection #columns }
+"Traversing a result set:"
+{ $subsection advance-row }
+{ $subsection more-rows? }
+"Pulling out a single row of results:"
+{ $subsection row-column }
+{ $subsection row-column-typed } ;
+
ARTICLE: "db-protocol" "Low-level database protocol"
"The high-level protocol (see " { $vocab-link "db.tuples" } ") uses this low-level protocol for executing statements and queries."
;
@@ -144,7 +283,6 @@ ARTICLE: "db-porting-the-library" "Porting the database library"
"This section is not yet written."
;
-
ARTICLE: "db-custom-database-combinators" "Custom database combinators"
"Every database library requires some effort on the programmer's part to initialize and open a database. SQLite uses files on your harddisk, so a simple pathname is all the setup required. With PostgreSQL, you log in to a networked server as a user on a specfic port." $nl
@@ -155,7 +293,6 @@ USING: db.sqlite db io.files ;
{ "my-database.db" temp-file } sqlite-db rot with-db ;
"> }
-
;
ABOUT: "db"
diff --git a/basis/db/db.factor b/basis/db/db.factor
index eac22a2999..87bf21d261 100755
--- a/basis/db/db.factor
+++ b/basis/db/db.factor
@@ -80,11 +80,14 @@ GENERIC: execute-statement* ( statement type -- )
M: object execute-statement* ( statement type -- )
drop query-results dispose ;
+: execute-one-statement ( statement -- )
+ dup type>> execute-statement* ;
+
: execute-statement ( statement -- )
dup sequence? [
- [ execute-statement ] each
+ [ execute-one-statement ] each
] [
- dup type>> execute-statement*
+ execute-one-statement
] if ;
: bind-statement ( obj statement -- )
diff --git a/basis/db/postgresql/postgresql.factor b/basis/db/postgresql/postgresql.factor
index 38fa4cc715..28548d1260 100755
--- a/basis/db/postgresql/postgresql.factor
+++ b/basis/db/postgresql/postgresql.factor
@@ -5,7 +5,7 @@ kernel math math.parser namespaces make prettyprint quotations
sequences debugger db db.postgresql.lib db.postgresql.ffi
db.tuples db.types tools.annotations math.ranges
combinators classes locals words tools.walker
-nmake accessors random db.queries destructors ;
+nmake accessors random db.queries destructors db.tuples.private ;
USE: tools.walker
IN: db.postgresql
@@ -37,8 +37,7 @@ M: postgresql-db db-open ( db -- db )
M: postgresql-db dispose ( db -- )
handle>> PQfinish ;
-M: postgresql-statement bind-statement* ( statement -- )
- drop ;
+M: postgresql-statement bind-statement* ( statement -- ) drop ;
GENERIC: postgresql-bind-conversion ( tuple object -- low-level-binding )
@@ -67,11 +66,11 @@ M: postgresql-result-set #columns ( result-set -- n )
[ handle>> ] [ n>> ] bi ;
M: postgresql-result-set row-column ( result-set column -- object )
- >r result-handle-n r> pq-get-string ;
+ [ result-handle-n ] dip pq-get-string ;
M: postgresql-result-set row-column-typed ( result-set column -- object )
dup pick out-params>> nth type>>
- >r >r result-handle-n r> r> postgresql-column-typed ;
+ [ result-handle-n ] 2dip postgresql-column-typed ;
M: postgresql-statement query-results ( query -- result-set )
dup bind-params>> [
@@ -126,13 +125,20 @@ M: postgresql-db bind# ( spec object -- )
: create-table-sql ( class -- statement )
[
+ dupd
"create table " 0% 0%
"(" 0% [ ", " 0% ] [
dup column-name>> 0%
" " 0%
dup type>> lookup-create-type 0%
modifiers 0%
- ] interleave ");" 0%
+ ] interleave
+
+ ", " 0%
+ find-primary-key
+ "primary key(" 0%
+ [ "," 0% ] [ column-name>> 0% ] interleave
+ "));" 0%
] query-make ;
: create-function-sql ( class -- statement )
@@ -160,8 +166,7 @@ M: postgresql-db bind# ( spec object -- )
M: postgresql-db create-sql-statement ( class -- seq )
[
[ create-table-sql , ] keep
- dup db-columns find-primary-key db-assigned-id-spec?
- [ create-function-sql , ] [ drop ] if
+ dup db-assigned? [ create-function-sql , ] [ drop ] if
] { } make ;
: drop-function-sql ( class -- statement )
@@ -181,15 +186,14 @@ M: postgresql-db create-sql-statement ( class -- seq )
M: postgresql-db drop-sql-statement ( class -- seq )
[
[ drop-table-sql , ] keep
- dup db-columns find-primary-key db-assigned-id-spec?
- [ drop-function-sql , ] [ drop ] if
+ dup db-assigned? [ drop-function-sql , ] [ drop ] if
] { } make ;
M: postgresql-db ( class -- statement )
[
"select add_" 0% 0%
"(" 0%
- dup find-primary-key 2,
+ dup find-primary-key first 2,
remove-id
[ ", " 0% ] [ bind% ] interleave
");" 0%
@@ -218,14 +222,23 @@ M: postgresql-db ( class -- statement )
");" 0%
] query-make ;
-M: postgresql-db insert-tuple* ( tuple statement -- )
+M: postgresql-db insert-tuple-set-key ( tuple statement -- )
query-modify-tuple ;
M: postgresql-db persistent-table ( -- hashtable )
H{
- { +db-assigned-id+ { "integer" "serial primary key" f } }
- { +user-assigned-id+ { f f "primary key" } }
- { +random-id+ { "bigint" "bigint primary key" f } }
+ { +db-assigned-id+ { "integer" "serial" f } }
+ { +user-assigned-id+ { f f f } }
+ { +random-id+ { "bigint" "bigint" f } }
+
+ { +foreign-id+ { f f "references" } }
+
+ { +on-delete+ { f f "on delete" } }
+ { +restrict+ { f f "restrict" } }
+ { +cascade+ { f f "cascade" } }
+ { +set-null+ { f f "set null" } }
+ { +set-default+ { f f "set default" } }
+
{ TEXT { "text" "text" f } }
{ VARCHAR { "varchar" "varchar" f } }
{ INTEGER { "integer" "integer" f } }
@@ -240,7 +253,6 @@ M: postgresql-db persistent-table ( -- hashtable )
{ BLOB { "bytea" "bytea" f } }
{ FACTOR-BLOB { "bytea" "bytea" f } }
{ URL { "varchar" "varchar" f } }
- { +foreign-id+ { f f "references" } }
{ +autoincrement+ { f f "autoincrement" } }
{ +unique+ { f f "unique" } }
{ +default+ { f f "default" } }
@@ -256,10 +268,6 @@ M: postgresql-db compound ( string object -- string' )
over {
{ "default" [ first number>string join-space ] }
{ "varchar" [ first number>string paren append ] }
- { "references" [
- first2 >r [ unparse join-space ] keep db-columns r>
- swap [ slot-name>> = ] with find nip
- column-name>> paren append
- ] }
+ { "references" [ >reference-string ] }
[ drop no-compound-found ]
} case ;
diff --git a/basis/db/queries/queries.factor b/basis/db/queries/queries.factor
index 89c28b5262..0b206cea8f 100644
--- a/basis/db/queries/queries.factor
+++ b/basis/db/queries/queries.factor
@@ -2,8 +2,8 @@
! See http://factorcode.org/license.txt for BSD license.
USING: accessors kernel math namespaces make sequences random
strings math.parser math.intervals combinators math.bitwise
-nmake db db.tuples db.types db.sql classes words shuffle arrays
-destructors continuations ;
+nmake db db.tuples db.types classes words shuffle arrays
+destructors continuations db.tuples.private prettyprint ;
IN: db.queries
GENERIC: where ( specs obj -- )
@@ -45,14 +45,22 @@ M: retryable execute-statement* ( statement type -- )
: sql-props ( class -- columns table )
[ db-columns ] [ db-table ] bi ;
-: query-make ( class quot -- )
- >r sql-props r>
- [ 0 sql-counter rot with-variable ] curry { "" { } { } } nmake
- maybe-make-retryable ; inline
+: query-make ( class quot -- statements )
+ #! query, input, outputs, secondary queries
+ over unparse "table" set
+ [ sql-props ] dip
+ [ 0 sql-counter rot with-variable ] curry
+ { "" { } { } { } } nmake
+ [ maybe-make-retryable ] dip
+ [ [ 1array ] dip append ] unless-empty ; inline
: where-primary-key% ( specs -- )
" where " 0%
- find-primary-key dup column-name>> 0% " = " 0% bind% ;
+ find-primary-key [
+ " and " 0%
+ ] [
+ dup column-name>> 0% " = " 0% bind%
+ ] interleave ;
M: db ( class -- statement )
[
@@ -121,16 +129,15 @@ M: string where ( spec obj -- ) object-where ;
dup double-infinite-interval? [ drop f ] when
] with filter ;
-: where-clause ( tuple specs -- )
- dupd filter-slots [
- drop
+: many-where ( tuple seq -- )
+ " where " 0% [
+ " and " 0%
] [
- " where " 0% [
- " and " 0%
- ] [
- 2dup slot-name>> swap get-slot-named where
- ] interleave drop
- ] if-empty ;
+ 2dup slot-name>> swap get-slot-named where
+ ] interleave drop ;
+
+: where-clause ( tuple specs -- )
+ dupd filter-slots [ drop ] [ many-where ] if-empty ;
M: db ( tuple table -- sql )
[
@@ -141,34 +148,30 @@ M: db ( tuple table -- sql )
M: db ( tuple class -- statement )
[
"select " 0%
- over [ ", " 0% ]
+ [ dupd filter-ignores ] dip
+ over
+ [ ", " 0% ]
[ dup column-name>> 0% 2, ] interleave
-
" from " 0% 0%
where-clause
] query-make ;
+: splice ( string1 string2 string3 -- string )
+ swap 3append ;
+
: do-group ( tuple groups -- )
- [
- ", " join " group by " swap 3append
- ] curry change-sql drop ;
+ [ ", " join " group by " splice ] curry change-sql drop ;
: do-order ( tuple order -- )
- [
- ", " join " order by " swap 3append
- ] curry change-sql drop ;
+ [ ", " join " order by " splice ] curry change-sql drop ;
: do-offset ( tuple n -- )
- [
- number>string " offset " swap 3append
- ] curry change-sql drop ;
+ [ number>string " offset " splice ] curry change-sql drop ;
: do-limit ( tuple n -- )
- [
- number>string " limit " swap 3append
- ] curry change-sql drop ;
+ [ number>string " limit " splice ] curry change-sql drop ;
-: make-query ( tuple query -- tuple' )
+: make-query* ( tuple query -- tuple' )
dupd
{
[ group>> [ drop ] [ do-group ] if-empty ]
@@ -177,28 +180,16 @@ M: db ( tuple class -- statement )
[ offset>> [ do-offset ] [ drop ] if* ]
} 2cleave ;
-M: db ( tuple class query -- tuple )
- [ ] dip make-query ;
+M: db query>statement ( query -- tuple )
+ [ tuple>> dup class ] keep
+ [ ] dip make-query* ;
! select ID, NAME, SCORE from EXAM limit 1 offset 3
-: select-tuples* ( tuple -- statement )
- dup
- [
- select 0,
- dup class db-columns [ ", " 0, ]
- [ dup column-name>> 0, 2, ] interleave
- from 0,
- class name>> 0,
- ] { { } { } { } } nmake
- >r >r parse-sql 4drop r> r>
- maybe-make-retryable do-select ;
-
-M: db ( tuple class groups -- statement )
- \ query new
- swap >>group
+M: db ( query -- statement )
+ [ tuple>> dup class ] keep
[ [ "select count(*) from " 0% 0% where-clause ] query-make ]
- dip make-query ;
+ dip make-query* ;
: create-index ( index-name table-name columns -- )
[
diff --git a/basis/db/sqlite/sqlite.factor b/basis/db/sqlite/sqlite.factor
index 1eb9b566d3..dfd9fab08c 100755
--- a/basis/db/sqlite/sqlite.factor
+++ b/basis/db/sqlite/sqlite.factor
@@ -5,7 +5,8 @@ io.files kernel math math.parser namespaces prettyprint
sequences strings classes.tuple alien.c-types continuations
db.sqlite.lib db.sqlite.ffi db.tuples words db.types combinators
math.intervals io nmake accessors vectors math.ranges random
-math.bitwise db.queries destructors ;
+math.bitwise db.queries destructors db.tuples.private interpolate
+io.streams.string multiline make ;
IN: db.sqlite
TUPLE: sqlite-db < db path ;
@@ -88,7 +89,7 @@ M: sqlite-statement bind-tuple ( tuple statement -- )
db get handle>> sqlite3_last_insert_rowid
dup zero? [ "last-id failed" throw ] when ;
-M: sqlite-db insert-tuple* ( tuple statement -- )
+M: sqlite-db insert-tuple-set-key ( tuple statement -- )
execute-statement last-insert-id swap set-primary-key ;
M: sqlite-result-set #columns ( result-set -- n )
@@ -114,13 +115,21 @@ M: sqlite-statement query-results ( query -- result-set )
M: sqlite-db create-sql-statement ( class -- statement )
[
+ dupd
"create table " 0% 0%
"(" 0% [ ", " 0% ] [
- dup column-name>> 0%
+ dup "sql-spec" set
+ dup column-name>> [ "table-id" set ] [ 0% ] bi
" " 0%
dup type>> lookup-create-type 0%
modifiers 0%
- ] interleave ");" 0%
+ ] interleave
+
+ ", " 0%
+ find-primary-key
+ "primary key(" 0%
+ [ "," 0% ] [ column-name>> 0% ] interleave
+ "));" 0%
] query-make ;
M: sqlite-db drop-sql-statement ( class -- statement )
@@ -161,23 +170,31 @@ M: sqlite-db bind% ( spec -- )
M: sqlite-db persistent-table ( -- assoc )
H{
- { +db-assigned-id+ { "integer primary key" "integer primary key" "primary key" } }
- { +user-assigned-id+ { f f "primary key" } }
- { +random-id+ { "integer primary key" "integer primary key" "primary key" } }
- { INTEGER { "integer" "integer" "primary key" } }
- { BIG-INTEGER { "bigint" "bigint" } }
- { SIGNED-BIG-INTEGER { "bigint" "bigint" } }
- { UNSIGNED-BIG-INTEGER { "bigint" "bigint" } }
- { TEXT { "text" "text" } }
- { VARCHAR { "text" "text" } }
- { DATE { "date" "date" } }
- { TIME { "time" "time" } }
- { DATETIME { "datetime" "datetime" } }
- { TIMESTAMP { "timestamp" "timestamp" } }
- { DOUBLE { "real" "real" } }
- { BLOB { "blob" "blob" } }
- { FACTOR-BLOB { "blob" "blob" } }
- { URL { "text" "text" } }
+ { +db-assigned-id+ { "integer" "integer" f } }
+ { +user-assigned-id+ { f f f } }
+ { +random-id+ { "integer" "integer" f } }
+ { +foreign-id+ { "integer" "integer" "references" } }
+
+ { +on-delete+ { f f "on delete" } }
+ { +restrict+ { f f "restrict" } }
+ { +cascade+ { f f "cascade" } }
+ { +set-null+ { f f "set null" } }
+ { +set-default+ { f f "set default" } }
+
+ { INTEGER { "integer" "integer" f } }
+ { BIG-INTEGER { "bigint" "bigint" f } }
+ { SIGNED-BIG-INTEGER { "bigint" "bigint" f } }
+ { UNSIGNED-BIG-INTEGER { "bigint" "bigint" f } }
+ { TEXT { "text" "text" f } }
+ { VARCHAR { "text" "text" f } }
+ { DATE { "date" "date" f } }
+ { TIME { "time" "time" f } }
+ { DATETIME { "datetime" "datetime" f } }
+ { TIMESTAMP { "timestamp" "timestamp" f } }
+ { DOUBLE { "real" "real" f } }
+ { BLOB { "blob" "blob" f } }
+ { FACTOR-BLOB { "blob" "blob" f } }
+ { URL { "text" "text" f } }
{ +autoincrement+ { f f "autoincrement" } }
{ +unique+ { f f "unique" } }
{ +default+ { f f "default" } }
@@ -188,8 +205,110 @@ M: sqlite-db persistent-table ( -- assoc )
{ random-generator { f f f } }
} ;
-M: sqlite-db compound ( str seq -- str' )
+: insert-trigger ( -- string )
+ [
+ <"
+ CREATE TRIGGER fki_${table}_${foreign-table}_id
+ BEFORE INSERT ON ${table}
+ FOR EACH ROW BEGIN
+ SELECT RAISE(ROLLBACK, 'insert on table "${table}" violates foreign key constraint "fk_${foreign-table}_id"')
+ WHERE (SELECT ${foreign-table-id} FROM ${foreign-table} WHERE ${foreign-table-id} = NEW.${table-id}) IS NULL;
+ END;
+ "> interpolate
+ ] with-string-writer ;
+
+: insert-trigger-not-null ( -- string )
+ [
+ <"
+ CREATE TRIGGER fki_${table}_${foreign-table}_id
+ BEFORE INSERT ON ${table}
+ FOR EACH ROW BEGIN
+ SELECT RAISE(ROLLBACK, 'insert on table "${table}" violates foreign key constraint "fk_${foreign-table}_id"')
+ WHERE NEW.${foreign-table-id} IS NOT NULL
+ AND (SELECT ${foreign-table-id} FROM ${foreign-table} WHERE ${foreign-table-id} = NEW.${table-id}) IS NULL;
+ END;
+ "> interpolate
+ ] with-string-writer ;
+
+: update-trigger ( -- string )
+ [
+ <"
+ CREATE TRIGGER fku_${table}_${foreign-table}_id
+ BEFORE UPDATE ON ${table}
+ FOR EACH ROW BEGIN
+ SELECT RAISE(ROLLBACK, 'update on table "${table}" violates foreign key constraint "fk_${foreign-table}_id"')
+ WHERE (SELECT ${foreign-table-id} FROM ${foreign-table} WHERE ${foreign-table-id} = NEW.${table-id}) IS NULL;
+ END;
+ "> interpolate
+ ] with-string-writer ;
+
+: update-trigger-not-null ( -- string )
+ [
+ <"
+ CREATE TRIGGER fku_${table}_${foreign-table}_id
+ BEFORE UPDATE ON ${table}
+ FOR EACH ROW BEGIN
+ SELECT RAISE(ROLLBACK, 'update on table "${table}" violates foreign key constraint "fk_${foreign-table}_id"')
+ WHERE NEW.${foreign-table-id} IS NOT NULL
+ AND (SELECT ${foreign-table-id} FROM ${foreign-table} WHERE ${foreign-table-id} = NEW.${table-id}) IS NULL;
+ END;
+ "> interpolate
+ ] with-string-writer ;
+
+: delete-trigger-restrict ( -- string )
+ [
+ <"
+ CREATE TRIGGER fkd_${table}_${foreign-table}_id
+ BEFORE DELETE ON ${foreign-table}
+ FOR EACH ROW BEGIN
+ SELECT RAISE(ROLLBACK, 'delete on table "${foreign-table}" violates foreign key constraint "fk_${foreign-table}_id"')
+ WHERE (SELECT ${foreign-table-id} FROM ${foreign-table} WHERE ${foreign-table-id} = OLD.${foreign-table-id}) IS NOT NULL;
+ END;
+ "> interpolate
+ ] with-string-writer ;
+
+: delete-trigger-cascade ( -- string )
+ [
+ <"
+ CREATE TRIGGER fkd_${table}_${foreign-table}_id
+ BEFORE DELETE ON ${foreign-table}
+ FOR EACH ROW BEGIN
+ DELETE from ${table} WHERE ${table-id} = OLD.${foreign-table-id};
+ END;
+ "> interpolate
+ ] with-string-writer ;
+
+: can-be-null? ( -- ? )
+ "sql-spec" get modifiers>> [ +not-null+ = ] contains? not ;
+
+: delete-cascade? ( -- ? )
+ "sql-spec" get modifiers>> { +on-delete+ +cascade+ } swap subseq? ;
+
+: sqlite-trigger, ( string -- )
+ { } { } 3, ;
+
+: create-sqlite-triggers ( -- )
+ can-be-null? [
+ insert-trigger sqlite-trigger,
+ update-trigger sqlite-trigger,
+ ] [
+ insert-trigger-not-null sqlite-trigger,
+ update-trigger-not-null sqlite-trigger,
+ ] if
+ delete-cascade? [
+ delete-trigger-cascade sqlite-trigger,
+ ] [
+ delete-trigger-restrict sqlite-trigger,
+ ] if ;
+
+M: sqlite-db compound ( string seq -- new-string )
over {
{ "default" [ first number>string join-space ] }
- [ 2drop ]
+ { "references" [
+ [ >reference-string ] keep
+ first2 [ "foreign-table" set ]
+ [ "foreign-table-id" set ] bi*
+ create-sqlite-triggers
+ ] }
+ [ 2drop ]
} case ;
diff --git a/basis/db/tuples/tuples-docs.factor b/basis/db/tuples/tuples-docs.factor
index 26ecec0365..d7ee3a5ad2 100644
--- a/basis/db/tuples/tuples-docs.factor
+++ b/basis/db/tuples/tuples-docs.factor
@@ -1,7 +1,7 @@
! Copyright (C) 2008 Doug Coleman.
! See http://factorcode.org/license.txt for BSD license.
USING: classes help.markup help.syntax io.streams.string kernel
-quotations sequences strings multiline math ;
+quotations sequences strings multiline math db.types ;
IN: db.tuples
HELP: define-persistent
@@ -11,7 +11,18 @@ HELP: define-persistent
{ $list
{ "a slot name from the " { $snippet "tuple class" } }
{ "the name of a database column that maps to the slot" } { "a database type (see " { $link "db.types" } ")" }
-} } ;
+} "Throws an error if the slot name (column one from each row) is not a slot in the tuple or its superclases." }
+{ $examples
+ { $unchecked-example "USING: db.tuples db.types ;"
+ "TUPLE: boat id year name ;"
+ "boat \"BOAT\" {"
+ " { \"id\" \"ID\" +db-assigned-id+ }"
+ " { \"year\" \"YEAR\" INTEGER }"
+ " { \"name\" \"NAME\" TEXT }"
+ "} define-persistent"
+ ""
+ }
+} ;
HELP: create-table
{ $values
@@ -64,36 +75,35 @@ HELP: delete-tuples
HELP: select-tuple
{ $values
- { "tuple" tuple }
+ { "query/tuple" tuple }
{ "tuple/f" "a tuple or f" } }
{ $description "A SQL query is constructed from the slots of the exemplar tuple that are not " { $link f } ". Returns a single tuple from the database if it matches the query constructed from the exemplar tuple." } ;
HELP: select-tuples
{ $values
- { "tuple" tuple }
+ { "query/tuple" tuple }
{ "tuples" "an array of tuples" } }
{ $description "A SQL query is constructed from the slots of the exemplar tuple that are not " { $link f } ". Returns a multiple tuples from the database that match the query constructed from the exemplar tuple." } ;
HELP: count-tuples
{ $values
- { "tuple" tuple } { "groups" "an array of slots to group by" }
+ { "query/tuple" tuple }
{ "n" integer } }
-{ $description "" } ;
+{ $description "Returns the number of items that would be returned if the query were a select query. Counting the tuples with this word is more efficient than calling " { $link length } " on the result of " { $link select-tuples } "." } ;
+
+{ select-tuple select-tuples count-tuples } related-words
-HELP: query
-{ $values
- { "tuple" tuple } { "query" query }
- { "tuples" "a sequence of tuples" } }
-{ $description "Allows for queries with group by, order by, limit, and offset clauses. " } ;
-{ select-tuple select-tuples count-tuples query } related-words
ARTICLE: "db-tuples" "High-level tuple/database integration"
"Start with a tutorial:"
{ $subsection "db-tuples-tutorial" }
+"Database types supported:"
+{ $subsection "db.types" }
"Useful words:"
{ $subsection "db-tuples-words" }
-
+"For porting db.tuples to other databases:"
+{ $subsection "db-tuples-protocol" }
;
ARTICLE: "db-tuples-words" "High-level tuple/database words"
@@ -115,12 +125,9 @@ ARTICLE: "db-tuples-words" "High-level tuple/database words"
"Querying tuples:"
{ $subsection select-tuple }
{ $subsection select-tuples }
-{ $subsection count-tuples }
-"Advanced querying of tuples:"
-{ $subsection query } ;
+{ $subsection count-tuples } ;
-
-ARTICLE: "db-tuples-protocol" "High-level tuple/database protocol"
+ARTICLE: "db-tuples-protocol" "Tuple database protocol"
;
ARTICLE: "db-tuples-tutorial" "Tuple database tutorial"
diff --git a/basis/db/tuples/tuples-tests.factor b/basis/db/tuples/tuples-tests.factor
index 67e46f9e18..6114c7ebe1 100755
--- a/basis/db/tuples/tuples-tests.factor
+++ b/basis/db/tuples/tuples-tests.factor
@@ -4,9 +4,20 @@ USING: io.files kernel tools.test db db.tuples classes
db.types continuations namespaces math math.ranges
prettyprint calendar sequences db.sqlite math.intervals
db.postgresql accessors random math.bitwise
-math.ranges strings urls fry ;
+math.ranges strings urls fry db.tuples.private ;
IN: db.tuples.tests
+: test-sqlite ( quot -- )
+ [ ] swap '[
+ "tuples-test.db" temp-file sqlite-db _ with-db
+ ] unit-test ;
+
+: test-postgresql ( quot -- )
+ [ ] swap '[
+ { "localhost" "postgres" "foob" "factor-test" }
+ postgresql-db _ with-db
+ ] unit-test ;
+
TUPLE: person the-id the-name the-number the-real
ts date time blob factor-blob url ;
@@ -165,46 +176,124 @@ SYMBOL: person4
T{ timestamp f 0 0 0 12 34 56 T{ duration f 0 0 0 0 0 0 } }
f H{ { 1 2 } { 3 4 } { 5 "lol" } } URL" http://www.google.com/search?hl=en&q=trailer+park+boys&btnG=Google+Search" person4 set ;
+
TUPLE: paste n summary author channel mode contents timestamp annotations ;
TUPLE: annotation n paste-id summary author mode contents ;
-: db-assigned-paste-schema ( -- )
- paste "PASTE"
- {
- { "n" "ID" +db-assigned-id+ }
- { "summary" "SUMMARY" TEXT }
- { "author" "AUTHOR" TEXT }
- { "channel" "CHANNEL" TEXT }
- { "mode" "MODE" TEXT }
- { "contents" "CONTENTS" TEXT }
- { "date" "DATE" TIMESTAMP }
- { "annotations" { +has-many+ annotation } }
- } define-persistent
+paste "PASTE"
+{
+ { "n" "ID" +db-assigned-id+ }
+ { "summary" "SUMMARY" TEXT }
+ { "author" "AUTHOR" TEXT }
+ { "channel" "CHANNEL" TEXT }
+ { "mode" "MODE" TEXT }
+ { "contents" "CONTENTS" TEXT }
+ { "timestamp" "DATE" TIMESTAMP }
+ { "annotations" { +has-many+ annotation } }
+} define-persistent
+: annotation-schema-foreign-key ( -- )
annotation "ANNOTATION"
{
{ "n" "ID" +db-assigned-id+ }
- { "paste-id" "PASTE_ID" INTEGER { +foreign-id+ paste "n" } }
+ { "paste-id" "PASTE_ID" INTEGER { +foreign-id+ paste "ID" } }
{ "summary" "SUMMARY" TEXT }
{ "author" "AUTHOR" TEXT }
{ "mode" "MODE" TEXT }
{ "contents" "CONTENTS" TEXT }
} define-persistent ;
-! { "localhost" "postgres" "" "factor-test" } postgresql-db [
- ! [ paste drop-table ] [ drop ] recover
- ! [ annotation drop-table ] [ drop ] recover
- ! [ paste drop-table ] [ drop ] recover
- ! [ annotation drop-table ] [ drop ] recover
- ! [ ] [ paste create-table ] unit-test
- ! [ ] [ annotation create-table ] unit-test
-! ] with-db
+: annotation-schema-foreign-key-not-null ( -- )
+ annotation "ANNOTATION"
+ {
+ { "n" "ID" +db-assigned-id+ }
+ { "paste-id" "PASTE_ID" INTEGER { +foreign-id+ paste "ID" } +not-null+ }
+ { "summary" "SUMMARY" TEXT }
+ { "author" "AUTHOR" TEXT }
+ { "mode" "MODE" TEXT }
+ { "contents" "CONTENTS" TEXT }
+ } define-persistent ;
-: test-sqlite ( quot -- )
- [ ] swap '[ "tuples-test.db" temp-file sqlite-db _ with-db ] unit-test ;
+: annotation-schema-cascade ( -- )
+ annotation "ANNOTATION"
+ {
+ { "n" "ID" +db-assigned-id+ }
+ { "paste-id" "PASTE_ID" INTEGER { +foreign-id+ paste "ID" }
+ +on-delete+ +cascade+ }
+ { "summary" "SUMMARY" TEXT }
+ { "author" "AUTHOR" TEXT }
+ { "mode" "MODE" TEXT }
+ { "contents" "CONTENTS" TEXT }
+ } define-persistent ;
-: test-postgresql ( quot -- )
- [ ] swap '[ { "localhost" "postgres" "foob" "factor-test" } postgresql-db _ with-db ] unit-test ;
+: annotation-schema-restrict ( -- )
+ annotation "ANNOTATION"
+ {
+ { "n" "ID" +db-assigned-id+ }
+ { "paste-id" "PASTE_ID" INTEGER { +foreign-id+ paste "ID" } }
+ { "summary" "SUMMARY" TEXT }
+ { "author" "AUTHOR" TEXT }
+ { "mode" "MODE" TEXT }
+ { "contents" "CONTENTS" TEXT }
+ } define-persistent ;
+
+: test-paste-schema ( -- )
+ [ ] [ paste ensure-table ] unit-test
+ [ ] [ annotation ensure-table ] unit-test
+ [ ] [ annotation drop-table ] unit-test
+ [ ] [ paste drop-table ] unit-test
+ [ ] [ paste create-table ] unit-test
+ [ ] [ annotation create-table ] unit-test
+
+ [ ] [
+ paste new
+ "summary1" >>summary
+ "erg" >>author
+ "#lol" >>channel
+ "contents1" >>contents
+ now >>timestamp
+ insert-tuple
+ ] unit-test
+
+ [ ] [
+ annotation new
+ 1 >>paste-id
+ "annotation1" >>summary
+ "erg" >>author
+ "annotation contents" >>contents
+ insert-tuple
+ ] unit-test ;
+
+: test-foreign-key ( -- )
+ [ ] [ annotation-schema-foreign-key ] unit-test
+ test-paste-schema
+ [ paste new 1 >>n delete-tuples ] must-fail ;
+
+: test-foreign-key-not-null ( -- )
+ [ ] [ annotation-schema-foreign-key-not-null ] unit-test
+ test-paste-schema
+ [ paste new 1 >>n delete-tuples ] must-fail ;
+
+: test-cascade ( -- )
+ [ ] [ annotation-schema-cascade ] unit-test
+ test-paste-schema
+ [ ] [ paste new 1 >>n delete-tuples ] unit-test
+ [ 0 ] [ paste new select-tuples length ] unit-test ;
+
+: test-restrict ( -- )
+ [ ] [ annotation-schema-restrict ] unit-test
+ test-paste-schema
+ [ paste new 1 >>n delete-tuples ] must-fail ;
+
+[ test-foreign-key ] test-sqlite
+[ test-foreign-key-not-null ] test-sqlite
+[ test-cascade ] test-sqlite
+[ test-restrict ] test-sqlite
+
+[ test-foreign-key ] test-postgresql
+[ test-foreign-key-not-null ] test-postgresql
+[ test-cascade ] test-postgresql
+[ test-restrict ] test-postgresql
: test-repeated-insert
[ ] [ person ensure-table ] unit-test
@@ -236,6 +325,17 @@ TUPLE: exam id name score ;
exam boa ;
: test-intervals ( -- )
+ [
+ exam "EXAM"
+ {
+ { "idd" "ID" +db-assigned-id+ }
+ { "named" "NAME" TEXT }
+ { "score" "SCORE" INTEGER }
+ } define-persistent
+ ] [
+ seq>> { "idd" "named" } =
+ ] must-fail-with
+
exam "EXAM"
{
{ "id" "ID" +db-assigned-id+ }
@@ -250,6 +350,16 @@ TUPLE: exam id name score ;
[ ] [ T{ exam f f "Kenny" 60 } insert-tuple ] unit-test
[ ] [ T{ exam f f "Cartman" 41 } insert-tuple ] unit-test
+ [ 4 ]
+ [ T{ exam { name IGNORE } { score IGNORE } } select-tuples length ] unit-test
+
+ [ f ]
+ [ T{ exam { name IGNORE } { score IGNORE } } select-tuples first score>> ] unit-test
+
+ ! FIXME
+ ! [ f ]
+ ! [ T{ exam { name IGNORE } { score IGNORE } { id IGNORE } } select-tuples first score>> ] unit-test
+
[
{
T{ exam f 3 "Kenny" 60 }
@@ -346,7 +456,7 @@ TUPLE: exam id name score ;
T{ exam } select-tuples
] unit-test
- [ 4 ] [ T{ exam } f count-tuples ] unit-test ;
+ [ 4 ] [ T{ exam } count-tuples ] unit-test ;
TUPLE: bignum-test id m n o ;
: ( m n o -- obj )
@@ -499,3 +609,42 @@ string-encoding-test "STRING_ENCODING_TEST" {
\ ensure-table must-infer
\ create-table must-infer
\ drop-table must-infer
+
+: test-queries ( -- )
+ [ ] [ exam ensure-table ] unit-test
+ [ ] [ 1000 [ random-exam insert-tuple ] times ] unit-test
+ [ 5 ] [
+
+ T{ exam { score T{ interval { from { 0 t } } { to { 100 t } } } } }
+ >>tuple
+ 5 >>limit select-tuples length
+ ] unit-test ;
+
+TUPLE: compound-foo a b c ;
+
+compound-foo "COMPOUND_FOO"
+{
+ { "a" "A" INTEGER +user-assigned-id+ }
+ { "b" "B" INTEGER +user-assigned-id+ }
+ { "c" "C" INTEGER }
+} define-persistent
+
+: test-compound-primary-key ( -- )
+ [ ] [ compound-foo ensure-table ] unit-test
+ [ ] [ compound-foo drop-table ] unit-test
+ [ ] [ compound-foo create-table ] unit-test
+ [ ] [ 1 2 3 compound-foo boa insert-tuple ] unit-test
+ [ 1 2 3 compound-foo boa insert-tuple ] must-fail
+ [ ] [ 2 3 4 compound-foo boa insert-tuple ] unit-test
+ [ T{ compound-foo { a 2 } { b 3 } { c 4 } } ]
+ [ compound-foo new 4 >>c select-tuple ] unit-test ;
+
+[ test-compound-primary-key ] test-sqlite
+[ test-compound-primary-key ] test-postgresql
+
+: sqlite-test-db ( -- )
+ "tuples-test.db" temp-file sqlite-db make-db db-open db set ;
+
+: postgresql-test-db ( -- )
+ { "localhost" "postgres" "foob" "factor-test" } postgresql-db
+ make-db db-open db set ;
diff --git a/basis/db/tuples/tuples.factor b/basis/db/tuples/tuples.factor
index 3c3bae3adc..7f567697d2 100755
--- a/basis/db/tuples/tuples.factor
+++ b/basis/db/tuples/tuples.factor
@@ -3,36 +3,10 @@
USING: arrays assocs classes db kernel namespaces
classes.tuple words sequences slots math accessors
math.parser io prettyprint db.types continuations
-destructors mirrors ;
+destructors mirrors sets db.types ;
IN: db.tuples
-: define-persistent ( class table columns -- )
- >r dupd "db-table" set-word-prop dup r>
- [ relation? ] partition swapd
- dupd [ spec>tuple ] with map
- "db-columns" set-word-prop
- "db-relations" set-word-prop ;
-
-ERROR: not-persistent class ;
-
-: db-table ( class -- object )
- dup "db-table" word-prop [ ] [ not-persistent ] ?if ;
-
-: db-columns ( class -- object )
- superclasses [ "db-columns" word-prop ] map concat ;
-
-: db-relations ( class -- object )
- "db-relations" word-prop ;
-
-: set-primary-key ( key tuple -- )
- [
- class db-columns find-primary-key slot-name>>
- ] keep set-slot-named ;
-
-SYMBOL: sql-counter
-: next-sql-counter ( -- str )
- sql-counter [ inc ] [ get ] bi number>string ;
-
+ db ( class -- object )
HOOK: db ( class -- object )
HOOK: db ( tuple class -- object )
HOOK: db ( tuple class -- tuple )
-TUPLE: query group order offset limit ;
-HOOK: db ( tuple class query -- statement' )
-HOOK: db ( tuple class groups -- n )
+HOOK: db ( query -- statement )
+HOOK: query>statement db ( query -- statement )
-HOOK: insert-tuple* db ( tuple statement -- )
+HOOK: insert-tuple-set-key db ( tuple statement -- )
+
+SYMBOL: sql-counter
+: next-sql-counter ( -- str )
+ sql-counter [ inc ] [ get ] bi number>string ;
GENERIC: eval-generator ( singleton -- object )
: resulting-tuple ( exemplar-tuple row out-params -- tuple )
rot class new [
- [
- [ slot-name>> ] dip set-slot-named
- ] curry 2each
+ [ [ slot-name>> ] dip set-slot-named ] curry 2each
] keep ;
: query-tuples ( exemplar-tuple statement -- seq )
@@ -75,6 +50,51 @@ GENERIC: eval-generator ( singleton -- object )
with-disposal
] if ; inline
+: insert-db-assigned-statement ( tuple -- )
+ dup class
+ db get insert-statements>> [ ] cache
+ [ bind-tuple ] 2keep insert-tuple-set-key ;
+
+: insert-user-assigned-statement ( tuple -- )
+ dup class
+ db get insert-statements>> [ ] cache
+ [ bind-tuple ] keep execute-statement ;
+
+: do-select ( exemplar-tuple statement -- tuples )
+ [ [ bind-tuple ] [ query-tuples ] 2bi ] with-disposal ;
+
+: do-count ( exemplar-tuple statement -- tuples )
+ [ [ bind-tuple ] [ nip default-query ] 2bi ] with-disposal ;
+PRIVATE>
+
+
+! High level
+ERROR: no-slots-named class seq ;
+: check-columns ( class columns -- )
+ tuck
+ [ [ first ] map ]
+ [ all-slots [ name>> ] map ] bi* diff
+ [ drop ] [ no-slots-named ] if-empty ;
+
+: define-persistent ( class table columns -- )
+ pick dupd
+ check-columns
+ [ dupd "db-table" set-word-prop dup ] dip
+ [ relation? ] partition swapd
+ dupd [ spec>tuple ] with map
+ "db-columns" set-word-prop
+ "db-relations" set-word-prop ;
+
+TUPLE: query tuple group order offset limit ;
+
+: ( -- query ) \ query new ;
+
+GENERIC: >query ( object -- query )
+
+M: query >query clone ;
+
+M: tuple >query swap >>tuple ;
+
: create-table ( class -- )
create-sql-statement [ execute-statement ] with-disposals ;
@@ -87,21 +107,9 @@ GENERIC: eval-generator ( singleton -- object )
] curry ignore-errors
] [ create-table ] bi ;
-: ensure-table ( class -- )
- [ create-table ] curry ignore-errors ;
+: ensure-table ( class -- ) [ create-table ] curry ignore-errors ;
-: ensure-tables ( classes -- )
- [ ensure-table ] each ;
-
-: insert-db-assigned-statement ( tuple -- )
- dup class
- db get insert-statements>> [ ] cache
- [ bind-tuple ] 2keep insert-tuple* ;
-
-: insert-user-assigned-statement ( tuple -- )
- dup class
- db get insert-statements>> [ ] cache
- [ bind-tuple ] keep execute-statement ;
+: ensure-tables ( classes -- ) [ ensure-table ] each ;
: insert-tuple ( tuple -- )
dup class db-columns find-primary-key db-assigned-id-spec?
@@ -117,25 +125,14 @@ GENERIC: eval-generator ( singleton -- object )
[ bind-tuple ] keep execute-statement
] with-disposal ;
-: do-select ( exemplar-tuple statement -- tuples )
- [ [ bind-tuple ] [ query-tuples ] 2bi ] with-disposal ;
+: select-tuples ( query/tuple -- tuples )
+ >query [ tuple>> ] [ query>statement ] bi do-select ;
-: query ( tuple query -- tuples )
- [ dup dup class ] dip do-select ;
-
-: select-tuples ( tuple -- tuples )
- dup dup class do-select ;
-
-: select-tuple ( tuple -- tuple/f )
- dup dup class \ query new 1 >>limit do-select
+: select-tuple ( query/tuple -- tuple/f )
+ >query 1 >>limit [ tuple>> ] [ query>statement ] bi do-select
[ f ] [ first ] if-empty ;
-: do-count ( exemplar-tuple statement -- tuples )
- [
- [ bind-tuple ] [ nip default-query ] 2bi
- ] with-disposal ;
-
-: count-tuples ( tuple groups -- n )
- >r dup dup class r> do-count
+: count-tuples ( query/tuple -- n )
+ >query [ tuple>> ] [ ] bi do-count
dup length 1 =
[ first first string>number ] [ [ first string>number ] map ] if ;
diff --git a/basis/db/types/types-docs.factor b/basis/db/types/types-docs.factor
index 9300a68f2e..401bbbc4d7 100644
--- a/basis/db/types/types-docs.factor
+++ b/basis/db/types/types-docs.factor
@@ -1,14 +1,9 @@
! Copyright (C) 2008 Doug Coleman.
! See http://factorcode.org/license.txt for BSD license.
-USING: classes hashtables help.markup help.syntax io.streams.string kernel sequences strings ;
+USING: classes hashtables help.markup help.syntax io.streams.string
+kernel sequences strings math ;
IN: db.types
-HELP: (lookup-type)
-{ $values
- { "obj" object }
- { "string" string } }
-{ $description "" } ;
-
HELP: +autoincrement+
{ $description "" } ;
@@ -55,7 +50,7 @@ HELP:
{ $description "" } ;
HELP: BIG-INTEGER
-{ $description "A 64-bit integer." } ;
+{ $description "A 64-bit integer. Whether this number is signed or unsigned depends on the database backend." } ;
HELP: BLOB
{ $description "A serialized Factor object. The database library automatically serializes the object for a SQL insert or update and deserializes it on a tuple query." } ;
@@ -73,13 +68,13 @@ HELP: DOUBLE
{ $description "Corresponds to Factor's 64bit floating-point numbers." } ;
HELP: FACTOR-BLOB
-{ $description "" } ;
+{ $description "A serialized Factor object." } ;
HELP: INTEGER
-{ $description "" } ;
+{ $description "A small integer, at least 32 bits in length. Whether this number is signed or unsigned depends on the database backend." } ;
HELP: NULL
-{ $description "" } ;
+{ $description "The SQL null type." } ;
HELP: REAL
{ $description "" } ;
@@ -94,22 +89,24 @@ HELP: TIME
{ $description "" } ;
HELP: TIMESTAMP
-{ $description "" } ;
+{ $description "A Factor timestamp." } ;
HELP: UNSIGNED-BIG-INTEGER
-{ $description "" } ;
+{ $description "For portability, if a number is known to be 64bit, then this datatype may be used. Some databases, like SQLite, cannot store arbitrary bignums as BIGINT types. If storing arbitrary bignums, use " { $link FACTOR-BLOB } "." } ;
+
+{ INTEGER SIGNED-BIG-INTEGER UNSIGNED-BIG-INTEGER } related-words
HELP: URL
-{ $description "" } ;
+{ $description "A Factor " { $link "urls" } " object." } ;
HELP: VARCHAR
-{ $description "" } ;
+{ $description "The SQL varchar type. This type can take an integer as an argument." } ;
-HELP: assigned-id-spec?
+HELP: user-assigned-id-spec?
{ $values
- { "spec" null }
+ { "specs" "a sequence of sql specs" }
{ "?" "a boolean" } }
-{ $description "" } ;
+{ $description "Tests if any of the sql specs has the type " { $link +user-assigned-id+ } "." } ;
HELP: bind#
{ $values
@@ -129,24 +126,25 @@ HELP: compound
HELP: db-assigned-id-spec?
{ $values
- { "spec" null }
+ { "specs" "a sequence of sql specs" }
{ "?" "a boolean" } }
-{ $description "" } ;
+{ $description "Tests if any of the sql specs has the type " { $link +db-assigned-id+ } "." } ;
HELP: find-primary-key
{ $values
- { "specs" null }
- { "obj" object } }
-{ $description "" } ;
+ { "specs" "a sequence of sql-specs" }
+ { "seq" "a sequence of sql-specs" } }
+{ $description "Returns the rows from the sql-specs array that are part of the primary key. Composite primary keys are supported, so this word must return a sequence." }
+{ $notes "This is a low-level word." } ;
HELP: generator-bind
{ $description "" } ;
HELP: get-slot-named
{ $values
- { "name" null } { "obj" object }
- { "value" null } }
-{ $description "" } ;
+ { "name" "a slot name" } { "tuple" tuple }
+ { "value" "the value stored in the slot" } }
+{ $description "Returns the value stored in a tuple slot, where the tuple slot is a string." } ;
HELP: join-space
{ $values
@@ -185,30 +183,20 @@ HELP: modifiers
{ $description "" } ;
HELP: no-sql-type
-{ $description "" } ;
+{ $values
+ { "type" "a sql type" } }
+{ $description "Throws an error containing a sql type that is unsupported or the result of a typo." } ;
HELP: normalize-spec
{ $values
{ "spec" null } }
{ $description "" } ;
-HELP: number>string*
-{ $values
- { "n/string" null }
- { "string" string } }
-{ $description "" } ;
-
HELP: offset-of-slot
{ $values
- { "string" string } { "obj" object }
- { "n" null } }
-{ $description "" } ;
-
-HELP: paren
-{ $values
- { "string" string }
- { "new-string" null } }
-{ $description "" } ;
+ { "string" string } { "tuple" tuple }
+ { "n" integer } }
+{ $description "Returns the offset of a tuple slot accessed by name." } ;
HELP: persistent-table
{ $values
@@ -264,7 +252,8 @@ HELP: sql-spec
{ $description "" } ;
HELP: unknown-modifier
-{ $description "" } ;
+{ $values { "modifier" string } }
+{ $description "Throws an error containing an unknown sql modifier." } ;
ARTICLE: "db.types" "Database types"
"The " { $vocab-link "db.types" } " vocabulary maps Factor types to database types." $nl
@@ -294,7 +283,6 @@ ARTICLE: "db.types" "Database types"
{ $subsection BLOB }
{ $subsection FACTOR-BLOB }
"Factor URLs:"
-{ $subsection URL }
-;
+{ $subsection URL } ;
ABOUT: "db.types"
diff --git a/basis/db/types/types.factor b/basis/db/types/types.factor
index 476d82a1e2..ac9e3397f8 100755
--- a/basis/db/types/types.factor
+++ b/basis/db/types/types.factor
@@ -1,7 +1,7 @@
! Copyright (C) 2008 Doug Coleman.
! See http://factorcode.org/license.txt for BSD license.
USING: arrays assocs db kernel math math.parser
-sequences continuations sequences.deep
+sequences continuations sequences.deep prettyprint
words namespaces slots slots.private classes mirrors
classes.tuple combinators calendar.format symbols
classes.singleton accessors quotations random ;
@@ -22,22 +22,59 @@ SINGLETON: random-id-generator
TUPLE: low-level-binding value ;
C: low-level-binding
-SINGLETON: +db-assigned-id+
-SINGLETON: +user-assigned-id+
-SINGLETON: +random-id+
+SINGLETONS: +db-assigned-id+ +user-assigned-id+ +random-id+ ;
UNION: +primary-key+ +db-assigned-id+ +user-assigned-id+ +random-id+ ;
SYMBOLS: +autoincrement+ +serial+ +unique+ +default+ +null+ +not-null+
-+foreign-id+ +has-many+ ;
++foreign-id+ +has-many+ +on-delete+ +restrict+ +cascade+ +set-null+
++set-default+ ;
+
+SYMBOL: IGNORE
+
+: filter-ignores ( tuple specs -- specs' )
+ [ [ nip IGNORE = ] assoc-filter keys ] dip
+ [ slot-name>> swap member? not ] with filter ;
+
+ERROR: no-slot ;
+
+: offset-of-slot ( string tuple -- n )
+ class superclasses [ "slots" word-prop ] map concat
+ slot-named dup [ no-slot ] unless offset>> ;
+
+: get-slot-named ( name tuple -- value )
+ tuck offset-of-slot slot ;
+
+: set-slot-named ( value name obj -- )
+ tuck offset-of-slot set-slot ;
+
+ERROR: not-persistent class ;
+
+: db-table ( class -- object )
+ dup "db-table" word-prop [ ] [ not-persistent ] ?if ;
+
+: db-columns ( class -- object )
+ superclasses [ "db-columns" word-prop ] map concat ;
+
+: db-relations ( class -- object )
+ "db-relations" word-prop ;
+
+: find-primary-key ( specs -- seq )
+ [ primary-key>> ] filter ;
+
+: set-primary-key ( value tuple -- )
+ [
+ class db-columns
+ find-primary-key first slot-name>>
+ ] keep set-slot-named ;
: primary-key? ( spec -- ? )
primary-key>> +primary-key+? ;
-: db-assigned-id-spec? ( spec -- ? )
- primary-key>> +db-assigned-id+? ;
+: db-assigned-id-spec? ( specs -- ? )
+ [ primary-key>> +db-assigned-id+? ] contains? ;
-: assigned-id-spec? ( spec -- ? )
- primary-key>> +user-assigned-id+? ;
+: user-assigned-id-spec? ( specs -- ? )
+ [ primary-key>> +user-assigned-id+? ] contains? ;
: normalize-spec ( spec -- )
dup type>> dup +primary-key+? [
@@ -49,8 +86,8 @@ SYMBOLS: +autoincrement+ +serial+ +unique+ +default+ +null+ +not-null+
[ >>primary-key drop ] [ drop ] if*
] if ;
-: find-primary-key ( specs -- obj )
- [ primary-key>> ] find nip ;
+: db-assigned? ( class -- ? )
+ db-columns find-primary-key db-assigned-id-spec? ;
: relation? ( spec -- ? ) [ +has-many+ = ] deep-find ;
@@ -58,16 +95,17 @@ SYMBOLS: INTEGER BIG-INTEGER SIGNED-BIG-INTEGER UNSIGNED-BIG-INTEGER
DOUBLE REAL BOOLEAN TEXT VARCHAR DATE TIME DATETIME TIMESTAMP BLOB
FACTOR-BLOB NULL URL ;
-: spec>tuple ( class spec -- tuple )
- 3 f pad-right
- [ first3 ] keep 3 tail
+: ( class slot-name column-name type modifiers -- sql-spec )
sql-spec new
swap >>modifiers
swap >>type
swap >>column-name
swap >>slot-name
swap >>class
- dup normalize-spec ;
+ dup normalize-spec ;
+
+: spec>tuple ( class spec -- tuple )
+ 3 f pad-right [ first3 ] keep 3 tail ;
: number>string* ( n/string -- string )
dup number? [ number>string ] when ;
@@ -86,18 +124,21 @@ FACTOR-BLOB NULL URL ;
! PostgreSQL Types:
! http://developer.postgresql.org/pgdocs/postgres/datatype.html
-ERROR: unknown-modifier ;
+: ?at ( obj assoc -- value/obj ? )
+ dupd at* [ [ nip ] [ drop ] if ] keep ;
+
+ERROR: unknown-modifier modifier ;
: lookup-modifier ( obj -- string )
{
{ [ dup array? ] [ unclip lookup-modifier swap compound ] }
- [ persistent-table at* [ unknown-modifier ] unless third ]
+ [ persistent-table ?at [ unknown-modifier ] unless third ]
} cond ;
-ERROR: no-sql-type ;
+ERROR: no-sql-type type ;
: (lookup-type) ( obj -- string )
- persistent-table at* [ no-sql-type ] unless ;
+ persistent-table ?at [ no-sql-type ] unless ;
: lookup-type ( obj -- string )
dup array? [
@@ -126,12 +167,11 @@ ERROR: no-sql-type ;
HOOK: bind% db ( spec -- )
HOOK: bind# db ( spec obj -- )
-: offset-of-slot ( string obj -- n )
- class superclasses [ "slots" word-prop ] map concat
- slot-named offset>> ;
+ERROR: no-column column ;
-: get-slot-named ( name obj -- value )
- tuck offset-of-slot slot ;
-
-: set-slot-named ( value name obj -- )
- tuck offset-of-slot set-slot ;
+: >reference-string ( string pair -- string )
+ first2
+ [ [ unparse join-space ] [ db-columns ] bi ] dip
+ swap [ column-name>> = ] with find nip
+ [ no-column ] unless*
+ column-name>> paren append ;
diff --git a/basis/debugger/debugger.factor b/basis/debugger/debugger.factor
index b7fd34c5be..ec93a01c19 100755
--- a/basis/debugger/debugger.factor
+++ b/basis/debugger/debugger.factor
@@ -22,6 +22,9 @@ M: tuple error-help class ;
M: string error. print ;
+: :error ( -- )
+ error get error. ;
+
: :s ( -- )
error-continuation get data>> stack. ;
@@ -323,3 +326,5 @@ M: bad-effect summary
drop "Bad stack effect declaration" ;
M: bad-escape summary drop "Bad escape code" ;
+
+M: bad-literal-tuple summary drop "Bad literal tuple" ;
diff --git a/basis/editors/macvim/authors.txt b/basis/editors/macvim/authors.txt
new file mode 100644
index 0000000000..e091bb8164
--- /dev/null
+++ b/basis/editors/macvim/authors.txt
@@ -0,0 +1 @@
+John Benediktsson
diff --git a/basis/editors/macvim/macvim.factor b/basis/editors/macvim/macvim.factor
new file mode 100755
index 0000000000..b5f864dcd0
--- /dev/null
+++ b/basis/editors/macvim/macvim.factor
@@ -0,0 +1,13 @@
+USING: definitions io.launcher kernel math math.parser parser
+namespaces prettyprint editors make ;
+
+IN: editors.macvim
+
+: macvim-location ( file line -- )
+ drop
+ [ "open" , "-a" , "MacVim", , ] { } make
+ try-process ;
+
+[ macvim-location ] edit-hook set-global
+
+
diff --git a/basis/editors/macvim/summary.txt b/basis/editors/macvim/summary.txt
new file mode 100644
index 0000000000..894d635b47
--- /dev/null
+++ b/basis/editors/macvim/summary.txt
@@ -0,0 +1 @@
+MacVim editor integration
diff --git a/basis/editors/macvim/tags.txt b/basis/editors/macvim/tags.txt
new file mode 100644
index 0000000000..6bf68304bb
--- /dev/null
+++ b/basis/editors/macvim/tags.txt
@@ -0,0 +1 @@
+unportable
diff --git a/basis/editors/textedit/authors.txt b/basis/editors/textedit/authors.txt
new file mode 100644
index 0000000000..e091bb8164
--- /dev/null
+++ b/basis/editors/textedit/authors.txt
@@ -0,0 +1 @@
+John Benediktsson
diff --git a/basis/editors/textedit/summary.txt b/basis/editors/textedit/summary.txt
new file mode 100644
index 0000000000..1d72d10db0
--- /dev/null
+++ b/basis/editors/textedit/summary.txt
@@ -0,0 +1 @@
+TextEdit editor integration
diff --git a/basis/editors/textedit/tags.txt b/basis/editors/textedit/tags.txt
new file mode 100644
index 0000000000..6bf68304bb
--- /dev/null
+++ b/basis/editors/textedit/tags.txt
@@ -0,0 +1 @@
+unportable
diff --git a/basis/editors/textedit/textedit.factor b/basis/editors/textedit/textedit.factor
new file mode 100755
index 0000000000..6942e24534
--- /dev/null
+++ b/basis/editors/textedit/textedit.factor
@@ -0,0 +1,13 @@
+USING: definitions io.launcher kernel math math.parser parser
+namespaces prettyprint editors make ;
+
+IN: editors.textedit
+
+: textedit-location ( file line -- )
+ drop
+ [ "open" , "-a" , "TextEdit", , ] { } make
+ try-process ;
+
+[ textedit-location ] edit-hook set-global
+
+
diff --git a/basis/eval/authors.txt b/basis/eval/authors.txt
new file mode 100644
index 0000000000..1901f27a24
--- /dev/null
+++ b/basis/eval/authors.txt
@@ -0,0 +1 @@
+Slava Pestov
diff --git a/basis/eval/summary.txt b/basis/eval/summary.txt
new file mode 100644
index 0000000000..679f074e90
--- /dev/null
+++ b/basis/eval/summary.txt
@@ -0,0 +1 @@
+Ad-hoc evaluation of strings of code
diff --git a/basis/farkup/farkup-docs.factor b/basis/farkup/farkup-docs.factor
index f2d53d2362..8e7270cc01 100644
--- a/basis/farkup/farkup-docs.factor
+++ b/basis/farkup/farkup-docs.factor
@@ -9,7 +9,7 @@ HELP: write-farkup
{ $values { "string" string } }
{ $description "Parse a Farkup string and writes the resulting HTML to " { $link output-stream } "." } ;
-HELP: farkup ( string -- farkup )
+HELP: parse-farkup ( string -- farkup )
{ $values { "string" string } { "farkup" "a Farkup syntax tree node" } }
{ $description "Parses Farkup and outputs a tree of " { $link "farkup-ast" } "." } ;
@@ -18,7 +18,7 @@ HELP: (write-farkup)
{ $description "Writes a Farkup syntax tree as HTML on " { $link output-stream } "." } ;
ARTICLE: "farkup-ast" "Farkup syntax tree nodes"
-"The " { $link farkup } " word outputs a tree of nodes corresponding to the Farkup syntax of the input string. This tree can be programatically traversed and mutated before being passed on to " { $link write-farkup } "."
+"The " { $link parse-farkup } " word outputs a tree of nodes corresponding to the Farkup syntax of the input string. This tree can be programatically traversed and mutated before being passed on to " { $link write-farkup } "."
{ $subsection heading1 }
{ $subsection heading2 }
{ $subsection heading3 }
@@ -30,7 +30,8 @@ ARTICLE: "farkup-ast" "Farkup syntax tree nodes"
{ $subsection inline-code }
{ $subsection paragraph }
{ $subsection list-item }
-{ $subsection list }
+{ $subsection unordered-list }
+{ $subsection ordered-list }
{ $subsection table }
{ $subsection table-row }
{ $subsection link }
@@ -44,7 +45,7 @@ $nl
{ $subsection convert-farkup }
{ $subsection write-farkup }
"The syntax tree of a piece of Farkup can also be inspected and modified:"
-{ $subsection farkup }
+{ $subsection parse-farkup }
{ $subsection (write-farkup) }
{ $subsection "farkup-ast" } ;
diff --git a/basis/farkup/farkup-tests.factor b/basis/farkup/farkup-tests.factor
index e25fa34960..27911a8d13 100644
--- a/basis/farkup/farkup-tests.factor
+++ b/basis/farkup/farkup-tests.factor
@@ -11,13 +11,11 @@ link-no-follow? off
[ "Baz" ] [ "Baz" simple-link-title ] unit-test
[ ] [
- "abcd-*strong*\nasdifj\nweouh23ouh23"
- "paragraph" \ farkup rule parse drop
+ "abcd-*strong*\nasdifj\nweouh23ouh23" parse-farkup drop
] unit-test
[ ] [
- "abcd-*strong*\nasdifj\nweouh23ouh23\n"
- "paragraph" \ farkup rule parse drop
+ "abcd-*strong*\nasdifj\nweouh23ouh23\n" parse-farkup drop
] unit-test
[ "a-b
" ] [ "a-b" convert-farkup ] unit-test
@@ -37,22 +35,30 @@ link-no-follow? off
[ "bar\n
" ] [ "-foo\nbar\n" convert-farkup ] unit-test
+[ "- a-b
" ] [ "#a-b" convert-farkup ] unit-test
+[ "- foo
" ] [ "#foo" convert-farkup ] unit-test
+[ "- foo
\n
" ] [ "#foo\n" convert-farkup ] unit-test
+[ "- foo
\n- bar
" ] [ "#foo\n#bar" convert-farkup ] unit-test
+[ "- foo
\n- bar
\n
" ] [ "#foo\n#bar\n" convert-farkup ] unit-test
+
+[ "- foo
\n
bar\n
" ] [ "#foo\nbar\n" convert-farkup ] unit-test
+
[ "\n\n" ] [ "\n\n" convert-farkup ] unit-test
[ "\n\n" ] [ "\r\n\r\n" convert-farkup ] unit-test
[ "\n\n\n\n" ] [ "\r\r\r\r" convert-farkup ] unit-test
[ "\n\n\n" ] [ "\r\r\r" convert-farkup ] unit-test
[ "\n\n\n" ] [ "\n\n\n" convert-farkup ] unit-test
-[ "foo
bar
" ] [ "foo\n\nbar" convert-farkup ] unit-test
-[ "foo
bar
" ] [ "foo\r\n\r\nbar" convert-farkup ] unit-test
-[ "foo
bar
" ] [ "foo\r\rbar" convert-farkup ] unit-test
-[ "foo
bar
" ] [ "foo\r\r\nbar" convert-farkup ] unit-test
+[ "foo\n
bar
" ] [ "foo\n\nbar" convert-farkup ] unit-test
+[ "foo\n
bar
" ] [ "foo\r\n\r\nbar" convert-farkup ] unit-test
+[ "foo\n
bar
" ] [ "foo\r\rbar" convert-farkup ] unit-test
+[ "foo\n
bar
" ] [ "foo\r\r\nbar" convert-farkup ] unit-test
[ "\nbar\n
" ] [ "\nbar\n" convert-farkup ] unit-test
[ "\nbar\n
" ] [ "\rbar\r" convert-farkup ] unit-test
[ "\nbar\n
" ] [ "\r\nbar\r\n" convert-farkup ] unit-test
-[ "foo
bar
" ] [ "foo\n\n\nbar" convert-farkup ] unit-test
+[ "foo\n
bar
" ] [ "foo\n\n\nbar" convert-farkup ] unit-test
[ "" ] [ "" convert-farkup ] unit-test
@@ -107,7 +113,7 @@ link-no-follow? off
] [ "Feature comparison:\n|a|Factor|Java|Lisp|\n|Coolness|Yes|No|No|\n|Badass|Yes|No|No|\n|Enterprise|Yes|Yes|No|\n|Kosher|Yes|No|Yes|\n" convert-farkup ] unit-test
[
- "Feature comparison:
a | Factor | Java | Lisp |
Coolness | Yes | No | No |
Badass | Yes | No | No |
Enterprise | Yes | Yes | No |
Kosher | Yes | No | Yes |
"
+ "Feature comparison:\n
a | Factor | Java | Lisp |
Coolness | Yes | No | No |
Badass | Yes | No | No |
Enterprise | Yes | Yes | No |
Kosher | Yes | No | Yes |
"
] [ "Feature comparison:\n\n|a|Factor|Java|Lisp|\n|Coolness|Yes|No|No|\n|Badass|Yes|No|No|\n|Enterprise|Yes|Yes|No|\n|Kosher|Yes|No|Yes|\n" convert-farkup ] unit-test
[
@@ -118,3 +124,36 @@ link-no-follow? off
] unit-test
[ "a c
" ] [ "[[a]] [[b|c]]" convert-farkup ] unit-test
+
+[ "C++
" ] [ "[[C++]]" convert-farkup ] unit-test
+
+[ "<foo>
" ] [ "" convert-farkup ] unit-test
+
+[ "asdf\n
" ] [ "asdf\n-lol\n-haha" convert-farkup ] unit-test
+
+[ "asdf\n
" ]
+ [ "asdf\n\n-lol\n-haha" convert-farkup ] unit-test
+
+[ "
" ] [ "___" convert-farkup ] unit-test
+[ "
\n" ] [ "___\n" convert-farkup ] unit-test
+
+[ "before:\n
{ 1 2 3 } 1 tail\n
" ]
+[ "before:\n[factor{{ 1 2 3 } 1 tail}]" convert-farkup ] unit-test
+
+[ "Factor-rific!
" ]
+[ "[[Factor]]-rific!" convert-farkup ] unit-test
+
+[ "[ factor { 1 2 3 }]
" ]
+[ "[ factor { 1 2 3 }]" convert-farkup ] unit-test
+
+[ "paragraph\n
" ]
+[ "paragraph\n___" convert-farkup ] unit-test
+
+[ "paragraph\n a ___ b
" ]
+[ "paragraph\n a ___ b" convert-farkup ] unit-test
+
+[ "\n
" ]
+[ "\n- a\n___" convert-farkup ] unit-test
+
+[ "hello_world how are you today?\n
- hello_world how are you today?
" ]
+[ "hello_world how are you today?\n- hello_world how are you today?" convert-farkup ] unit-test
diff --git a/basis/farkup/farkup.factor b/basis/farkup/farkup.factor
index 4d6ac127ad..21e3c05d04 100644
--- a/basis/farkup/farkup.factor
+++ b/basis/farkup/farkup.factor
@@ -1,32 +1,36 @@
! Copyright (C) 2008 Doug Coleman.
! See http://factorcode.org/license.txt for BSD license.
-USING: accessors arrays combinators html.elements io io.streams.string
-kernel math memoize namespaces peg peg.ebnf prettyprint
-sequences sequences.deep strings xml.entities vectors splitting
-xmode.code2html ;
+USING: accessors arrays combinators html.elements io
+io.streams.string kernel math namespaces peg peg.ebnf
+sequences sequences.deep strings xml.entities
+vectors splitting xmode.code2html urls.encoding ;
IN: farkup
SYMBOL: relative-link-prefix
SYMBOL: disable-images?
SYMBOL: link-no-follow?
+SYMBOL: line-breaks?
-TUPLE: heading1 obj ;
-TUPLE: heading2 obj ;
-TUPLE: heading3 obj ;
-TUPLE: heading4 obj ;
-TUPLE: strong obj ;
-TUPLE: emphasis obj ;
-TUPLE: superscript obj ;
-TUPLE: subscript obj ;
-TUPLE: inline-code obj ;
-TUPLE: paragraph obj ;
-TUPLE: list-item obj ;
-TUPLE: list obj ;
-TUPLE: table obj ;
-TUPLE: table-row obj ;
+TUPLE: heading1 child ;
+TUPLE: heading2 child ;
+TUPLE: heading3 child ;
+TUPLE: heading4 child ;
+TUPLE: strong child ;
+TUPLE: emphasis child ;
+TUPLE: superscript child ;
+TUPLE: subscript child ;
+TUPLE: inline-code child ;
+TUPLE: paragraph child ;
+TUPLE: list-item child ;
+TUPLE: unordered-list child ;
+TUPLE: ordered-list child ;
+TUPLE: table child ;
+TUPLE: table-row child ;
TUPLE: link href text ;
TUPLE: image href text ;
TUPLE: code mode string ;
+TUPLE: line ;
+TUPLE: line-break ;
: absolute-url? ( string -- ? )
{ "http://" "https://" "ftp://" } [ head? ] with contains? ;
@@ -34,9 +38,9 @@ TUPLE: code mode string ;
: simple-link-title ( string -- string' )
dup absolute-url? [ "/" last-split1 swap or ] unless ;
-EBNF: farkup
+EBNF: parse-farkup
nl = ("\r\n" | "\r" | "\n") => [[ drop "\n" ]]
-2nl = nl nl
+whitespace = " " | "\t" | nl
heading1 = "=" (!("=" | nl).)+ "="
=> [[ second >string heading1 boa ]]
@@ -50,6 +54,10 @@ heading3 = "===" (!("=" | nl).)+ "==="
heading4 = "====" (!("=" | nl).)+ "===="
=> [[ second >string heading4 boa ]]
+heading = heading4 | heading3 | heading2 | heading1
+
+
+
strong = "*" (!("*" | nl).)+ "*"
=> [[ second >string strong boa ]]
@@ -65,8 +73,6 @@ subscript = "~" (!("~" | nl).)+ "~"
inline-code = "%" (!("%" | nl).)+ "%"
=> [[ second >string inline-code boa ]]
-escaped-char = "\" . => [[ second ]]
-
link-content = (!("|"|"]").)+
image-link = "[[image:" link-content "|" link-content "]]"
@@ -82,44 +88,73 @@ labelled-link = "[[" link-content "|" link-content "]]"
link = image-link | labelled-link | simple-link
-heading = heading4 | heading3 | heading2 | heading1
+escaped-char = "\" .
+ => [[ second 1string ]]
inline-tag = strong | emphasis | superscript | subscript | inline-code
| link | escaped-char
+
+
inline-delimiter = '*' | '_' | '^' | '~' | '%' | '\' | '['
-table-column = (list | (!(nl | inline-delimiter | '|').)+ | inline-tag | inline-delimiter ) '|'
+cell = (!(inline-delimiter | '|' | nl).)+
+ => [[ >string ]]
+
+table-column = (list | cell | inline-tag | inline-delimiter ) '|'
=> [[ first ]]
table-row = "|" (table-column)+
=> [[ second table-row boa ]]
table = ((table-row nl => [[ first ]] )+ table-row? | table-row)
=> [[ table boa ]]
-paragraph-item = ( table | (!(nl | code | heading | inline-delimiter | table ).) | inline-tag | inline-delimiter)+
-paragraph = ((paragraph-item nl => [[ first ]])+ nl+ => [[ first ]]
- | (paragraph-item nl)+ paragraph-item?
+text = (!(nl | code | heading | inline-delimiter | table ).)+
+ => [[ >string ]]
+
+paragraph-nl-item = nl list
+ | nl line
+ | nl => [[ line-breaks? get [ drop line-break new ] when ]]
+paragraph-item = (table | code | text | inline-tag | inline-delimiter)+
+paragraph = ((paragraph-item paragraph-nl-item)+ nl+ => [[ first ]]
+ | (paragraph-item paragraph-nl-item)+ paragraph-item?
| paragraph-item)
=> [[ paragraph boa ]]
-
-list-item = '-' ((!(inline-delimiter | nl).)+ | inline-tag)*
- => [[ second list-item boa ]]
-list = ((list-item nl)+ list-item? | list-item)
- => [[ list boa ]]
-code = '[' (!('{' | nl | '[').)+ '{' (!("}]").)+ "}]"
+
+list-item = (cell | inline-tag | inline-delimiter)*
+
+ordered-list-item = '#' list-item
+ => [[ second list-item boa ]]
+ordered-list = ((ordered-list-item nl)+ ordered-list-item? | ordered-list-item)
+ => [[ ordered-list boa ]]
+
+unordered-list-item = '-' list-item
+ => [[ second list-item boa ]]
+unordered-list = ((unordered-list-item nl)+ unordered-list-item? | unordered-list-item)
+ => [[ unordered-list boa ]]
+
+list = ordered-list | unordered-list
+
+
+line = '___'
+ => [[ drop line new ]]
+
+
+named-code
+ = '[' (!('{' | whitespace | '[').)+ '{' (!("}]").)+ "}]"
=> [[ [ second >string ] [ fourth >string ] bi code boa ]]
simple-code
= "[{" (!("}]").)+ "}]"
=> [[ second f swap code boa ]]
+code = named-code | simple-code
+
+
stand-alone
- = (code | simple-code | heading | list | table | paragraph | nl)*
+ = (line | code | heading | list | table | paragraph | nl)*
;EBNF
-
-
: invalid-url "javascript:alert('Invalid URL in farkup');" ;
: check-url ( href -- href' )
@@ -136,7 +171,7 @@ stand-alone
: write-link ( href text -- )
escape-link
- [ ]
+ [ ]
[ write ]
bi* ;
@@ -146,7 +181,7 @@ stand-alone
"Images are not allowed" write
] [
escape-link
- [
] bi*
+ [
] bi*
] if ;
: render-code ( string mode -- string' )
@@ -161,31 +196,33 @@ GENERIC: (write-farkup) ( farkup -- )
: ( string -- ) write ;
: ( string -- ) write ;
: in-tag. ( obj quot string -- ) [ call ] keep ; inline
-M: heading1 (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "h1" in-tag. ;
-M: heading2 (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "h2" in-tag. ;
-M: heading3 (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "h3" in-tag. ;
-M: heading4 (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "h4" in-tag. ;
-M: strong (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "strong" in-tag. ;
-M: emphasis (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "em" in-tag. ;
-M: superscript (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "sup" in-tag. ;
-M: subscript (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "sub" in-tag. ;
-M: inline-code (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "code" in-tag. ;
-M: list-item (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "li" in-tag. ;
-M: list (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "ul" in-tag. ;
-M: paragraph (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "p" in-tag. ;
-M: link (write-farkup) ( obj -- ) [ href>> ] [ text>> ] bi write-link ;
-M: image (write-farkup) ( obj -- ) [ href>> ] [ text>> ] bi write-image-link ;
-M: code (write-farkup) ( obj -- ) [ string>> ] [ mode>> ] bi render-code ;
+M: heading1 (write-farkup) [ child>> (write-farkup) ] "h1" in-tag. ;
+M: heading2 (write-farkup) [ child>> (write-farkup) ] "h2" in-tag. ;
+M: heading3 (write-farkup) [ child>> (write-farkup) ] "h3" in-tag. ;
+M: heading4 (write-farkup) [ child>> (write-farkup) ] "h4" in-tag. ;
+M: strong (write-farkup) [ child>> (write-farkup) ] "strong" in-tag. ;
+M: emphasis (write-farkup) [ child>> (write-farkup) ] "em" in-tag. ;
+M: superscript (write-farkup) [ child>> (write-farkup) ] "sup" in-tag. ;
+M: subscript (write-farkup) [ child>> (write-farkup) ] "sub" in-tag. ;
+M: inline-code (write-farkup) [ child>> (write-farkup) ] "code" in-tag. ;
+M: list-item (write-farkup) [ child>> (write-farkup) ] "li" in-tag. ;
+M: unordered-list (write-farkup) [ child>> (write-farkup) ] "ul" in-tag. ;
+M: ordered-list (write-farkup) [ child>> (write-farkup) ] "ol" in-tag. ;
+M: paragraph (write-farkup) [ child>> (write-farkup) ] "p" in-tag. ;
+M: link (write-farkup) [ href>> ] [ text>> ] bi write-link ;
+M: image (write-farkup) [ href>> ] [ text>> ] bi write-image-link ;
+M: code (write-farkup) [ string>> ] [ mode>> ] bi render-code ;
+M: line (write-farkup) drop
;
+M: line-break (write-farkup) drop
nl ;
M: table-row (write-farkup) ( obj -- )
- obj>> [ [ [ (write-farkup) ] "td" in-tag. ] each ] "tr" in-tag. ;
-M: table (write-farkup) ( obj -- ) [ obj>> (write-farkup) ] "table" in-tag. ;
-M: fixnum (write-farkup) ( obj -- ) write1 ;
-M: string (write-farkup) ( obj -- ) write ;
-M: vector (write-farkup) ( obj -- ) [ (write-farkup) ] each ;
-M: f (write-farkup) ( obj -- ) drop ;
+ child>> [ [ [ (write-farkup) ] "td" in-tag. ] each ] "tr" in-tag. ;
+M: table (write-farkup) [ child>> (write-farkup) ] "table" in-tag. ;
+M: string (write-farkup) escape-string write ;
+M: vector (write-farkup) [ (write-farkup) ] each ;
+M: f (write-farkup) drop ;
: write-farkup ( string -- )
- farkup (write-farkup) ;
+ parse-farkup (write-farkup) ;
: convert-farkup ( string -- string' )
- farkup [ (write-farkup) ] with-string-writer ;
+ parse-farkup [ (write-farkup) ] with-string-writer ;
diff --git a/basis/furnace/actions/actions.factor b/basis/furnace/actions/actions.factor
index cce098f208..7505b3c612 100755
--- a/basis/furnace/actions/actions.factor
+++ b/basis/furnace/actions/actions.factor
@@ -14,7 +14,8 @@ html.elements
html.components
html.components
html.templates.chloe
-html.templates.chloe.syntax ;
+html.templates.chloe.syntax
+html.templates.chloe.compiler ;
IN: furnace.actions
SYMBOL: params
@@ -29,7 +30,8 @@ SYMBOL: rest
] unless-empty ;
-CHLOE: validation-messages drop render-validation-messages ;
+CHLOE: validation-messages
+ drop [ render-validation-messages ] [code] ;
TUPLE: action rest authorize init display validate submit ;
@@ -77,14 +79,14 @@ TUPLE: action rest authorize init display validate submit ;
: revalidate-url ( -- url/f )
revalidate-url-key param
- dup [ >url [ same-host? ] keep and ] when ;
+ dup [ >url ensure-port [ same-host? ] keep and ] when ;
: validation-failed ( -- * )
post-request? revalidate-url and [
begin-conversation
nested-forms-key param " " split harvest nested-forms cset
form get form cset
-
+
] [ <400> ] if*
exit-with ;
diff --git a/basis/furnace/actions/authors.txt b/basis/furnace/actions/authors.txt
new file mode 100644
index 0000000000..1901f27a24
--- /dev/null
+++ b/basis/furnace/actions/authors.txt
@@ -0,0 +1 @@
+Slava Pestov
diff --git a/basis/furnace/actions/summary.txt b/basis/furnace/actions/summary.txt
new file mode 100644
index 0000000000..53b775adda
--- /dev/null
+++ b/basis/furnace/actions/summary.txt
@@ -0,0 +1 @@
+Actions and form validation
diff --git a/basis/furnace/actions/tags.txt b/basis/furnace/actions/tags.txt
new file mode 100644
index 0000000000..c0772185a0
--- /dev/null
+++ b/basis/furnace/actions/tags.txt
@@ -0,0 +1 @@
+web
diff --git a/basis/furnace/alloy/alloy.factor b/basis/furnace/alloy/alloy.factor
index 6f5f6fdbf6..decee690a3 100644
--- a/basis/furnace/alloy/alloy.factor
+++ b/basis/furnace/alloy/alloy.factor
@@ -3,6 +3,7 @@
USING: kernel sequences db.tuples alarms calendar db fry
furnace.db
furnace.cache
+furnace.asides
furnace.referrer
furnace.sessions
furnace.conversations
@@ -10,20 +11,24 @@ furnace.auth.providers
furnace.auth.login.permits ;
IN: furnace.alloy
-: ( responder db params -- responder' )
- '[
-
-
- _ _
-
- ] call ;
-
-: state-classes { session conversation permit } ; inline
+: state-classes { session aside conversation permit } ; inline
: init-furnace-tables ( -- )
state-classes ensure-tables
user ensure-table ;
+: ( responder db params -- responder' )
+ [ [ init-furnace-tables ] with-db ]
+ [
+ [
+
+
+
+ ] 2dip
+
+
+ ] 2bi ;
+
: start-expiring ( db params -- )
'[
_ _ [ state-classes [ expire-state ] each ] with-db
diff --git a/basis/furnace/alloy/authors.txt b/basis/furnace/alloy/authors.txt
new file mode 100644
index 0000000000..1901f27a24
--- /dev/null
+++ b/basis/furnace/alloy/authors.txt
@@ -0,0 +1 @@
+Slava Pestov
diff --git a/basis/furnace/alloy/summary.txt b/basis/furnace/alloy/summary.txt
new file mode 100644
index 0000000000..7bad952903
--- /dev/null
+++ b/basis/furnace/alloy/summary.txt
@@ -0,0 +1 @@
+Convenience responder combines several features
diff --git a/basis/furnace/alloy/tags.txt b/basis/furnace/alloy/tags.txt
new file mode 100644
index 0000000000..c0772185a0
--- /dev/null
+++ b/basis/furnace/alloy/tags.txt
@@ -0,0 +1 @@
+web
diff --git a/basis/furnace/asides/asides.factor b/basis/furnace/asides/asides.factor
new file mode 100644
index 0000000000..6d4196cf0b
--- /dev/null
+++ b/basis/furnace/asides/asides.factor
@@ -0,0 +1,111 @@
+! Copyright (C) 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: namespaces assocs kernel sequences accessors hashtables
+urls db.types db.tuples math.parser fry logging combinators
+html.templates.chloe.syntax
+http http.server http.server.filters http.server.redirection
+furnace
+furnace.cache
+furnace.sessions
+furnace.redirection ;
+IN: furnace.asides
+
+TUPLE: aside < server-state
+session method url post-data ;
+
+: