Skip to content

Commit

Permalink
Merge pull request #347 from sabracrolleton/master
Browse files Browse the repository at this point in the history
Added define-dao-class macro docs
  • Loading branch information
sabracrolleton authored Apr 21, 2024
2 parents fd9b89b + e97b17f commit 722bf60
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 64 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Changelog 1.33.12
## Changes to Postmodern
- Added :define-dao-class macro (including docs and tests)
- Added more documentation on connections (use with-connection rather than toplevel-connect if creating an executable.
## Minor additions to cl-postgres tests
# Changelog 1.33.11
## Changes to S-SQL
- Added :analyze operator
Expand Down
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A Common Lisp PostgreSQL programming interface

---

Version 1.33.11
Version 1.33.12

Postmodern is a Common Lisp library for interacting with [PostgreSQL](http://www.postgresql.org) databases. It is under active development. Features are:

Expand Down Expand Up @@ -103,17 +103,17 @@ Assuming you have already installed it, first load and use the system:
(use-package :postmodern)
```

If you have a PostgreSQL server running on localhost, with a database called 'testdb' on it, which is accessible for user 'foucault' with password 'surveiller', there are two basic ways to connect
to a database. If your role/application/database(s) looks like a 1:1 relationship and you are not using threads, you can connect like this:
### Connections
#### Single Long Life Connections With No Multi-threading or Executable Creation
If you have a PostgreSQL server running on localhost, with a database called 'testdb' on it, which is accessible for user 'foucault' with password 'surveiller', there are two basic ways to connect to a database. If your role/application/database(s) looks like a 1:1 relationship and you are not using threads and you are not going to create an executable, you can connect like this:

```lisp
(connect-toplevel "testdb" "foucault" "surveiller" "localhost")
```

Which will establish a connection to be used by all code, except for that wrapped in a with-connection form, which takes the same arguments but only establishes the connection within
that lexical scope.
This will establish a connection to be used by all code, except for that wrapped in a with-connection form, which takes the same arguments but only establishes the connection within that lexical scope. This method is often used in development or debugging.

Connect-toplevel will maintain a single connection for the life of the session.
Connect-toplevel will maintain a single connection for the life of your running lisp instance.

If the Postgresql server is running on a port other than 5432, you would also pass the appropriate keyword port parameter. E.g.:

Expand All @@ -132,14 +132,17 @@ Ssl connections would similarly use the keyword parameter :use-ssl and pass :yes
When using ssl, you can set the cl-postgres exported variables \*ssl-certificate-file\*, \*ssl-key-file\* and \*ssl-root-ca-file\* to provide client key, certificate files
and root ca files. They can be either NIL, for no file, or a pathname.

#### Multiple Connections, Multi-threading or Executable Creation
If you have multiple roles connecting to one or more databases, i.e. 1:many or
many:1, (in other words, changing connections) or you are using threads (each thread will need to have its own connection) then with-connection form which establishes a connection with a lexical scope is more appropriate.
many:1, (in other words, changing connections) or you are using threads (each thread will need to have its own connection) or you are going to create an executable, then with-connection form which establishes a connection with a lexical scope is more appropriate.

```lisp
(with-connection '("testdb" "foucault" "surveiller" "localhost")
...)
```

The same additional parameters apply to specifying ports or establishing an ssl connection.

If you are creating a database, you need to have established a connection
to a currently existing database (typically "postgres"). Assuming the foucault role
is a superuser and you want to stay in a development connection with your new database
Expand All @@ -158,6 +161,7 @@ Note: (create-database) functionality is new to postmodern v. 1.32. Setting the
:limit-public-access parameter to t will block connections to that database from
anyone who you have not explicitly given permission (except other superusers).

#### Pooling Connections
A word about Postgresql connections. Postgresql connections are not lightweight
threads. They actually consume about 10 MB of memory per connection. In
addition, any connections which require security (ssl or scram authentication)
Expand All @@ -182,6 +186,7 @@ To use postmodern's simple connection pooler, the with-connection call would loo
The maximum number of connections in the pool is set in the special variable
\*max-pool-size\*, which defaults to nil (no maximum).

### Basic Queries
Now for a basic sanity test which does not need a database connection at all:

```lisp
Expand Down Expand Up @@ -214,6 +219,7 @@ You do not have to pull in the whole result of a query at once, you can also ite
(format t "On this row, x = ~A and y = ~A.~%" x y))
```

### DAO Classes
You can work directly with the database or you can use a simple [database-access-class](https://marijnhaverbeke.nl/postmodern/dao-classes.html) (aka dao) which would cover all the columns in a row. This is what a database-access class looks like:

```lisp
Expand Down Expand Up @@ -295,6 +301,20 @@ Now you can see why the double parens.
We also specify that the table name is not "country" but "countries". (Some style guides
recommend that table names be plural and references to rows be singular.)

### Define-Dao-Class Macro (New to version 1.33.12)
New to Postmodern version 1.33.12 (thank you Killianmh) is a macro that makes defining a dao class slightly easier. It is like defclass except two postmodern specific changes:

1. The dao-class metaclass options is automatically added.
2. If second value in a slot is not a keyword, it is assumed to be col-type.
Example:

```lisp
(define-dao-class id-class ()
((id integer :initarg :id :accessor test-id)
(email :col-type text :initarg :email :accessor email))
(:keys id))
```

### Table Creation

You can create tables directly without the need to define a class, and in more
Expand Down Expand Up @@ -373,7 +393,7 @@ described above. Using the slightly more complicated version of the country dao

This defines our table in the database. execute works like query, but does not expect any results back.

See [Introduction to Multi-table dao class objects](doc/dao-classes.html#multi-table-dao-class-object) in the postmodern.org or postmodern.html manual for a further discussion of multi-table use of daos.
See [Introduction to Multi-table dao class objects](https://marijnhaverbeke.nl/postmodern/dao-classes.html#multi-table-dao-class-object) in the postmodern.org or postmodern.html manual for a further discussion of multi-table use of daos.

### Inserting Data

Expand Down
2 changes: 1 addition & 1 deletion cl-postgres.asd
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
(test-op "cl-postgres/simple-date-tests"))))

(defsystem "cl-postgres/tests"
:depends-on ("cl-postgres" "fiveam" "uiop")
:depends-on ("cl-postgres" "fiveam" "uiop" "trivial-octet-streams")
:components
((:module "cl-postgres/tests"
:components ((:file "test-package")
Expand Down
1 change: 1 addition & 0 deletions cl-postgres/package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
#:db-division-by-zero
#:undefined-column
#:undefined-table
#:unrecognized-configuration-parameter
#:duplicate-column
#:duplicate-cursor
#:duplicate-database
Expand Down
Binary file modified cl-postgres/tests/test-clp-utf8.lisp
Binary file not shown.
31 changes: 17 additions & 14 deletions cl-postgres/trivial-utf-8.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ encoded form of that character."

(defun string-to-utf-8-bytes (string &key null-terminate)
"Convert a string into an array of unsigned bytes containing its
utf-8 representation."
utf-8 representation. If NULL-TERMINATE, add an extra 0 byte at the end."
(declare (type string string)
#.*optimize*)
(let ((buffer (make-array (+ (the fixnum (utf-8-byte-length string))
Expand Down Expand Up @@ -105,7 +105,7 @@ starting with a given byte."
((= (logand byte #b11110000) #b11100000) 3)
((= (logand byte #b11111000) #b11110000) 4)
(t (error 'utf-8-decoding-error :byte byte
:message "Invalid byte at start of character: 0x~X"))))
:message "UTF-8-group-size Invalid byte at start of character: 0x~X"))))

(defun utf-8-string-length (bytes &key (start 0) (end (length bytes)))
"Calculate the length of the string encoded by the given bytes."
Expand All @@ -132,7 +132,8 @@ extract the character starting at the given start position."
(six-bits (byte)
(unless (= (logand byte #b11000000) #b10000000)
(error 'utf-8-decoding-error :byte byte
:message "Invalid byte 0x~X inside a character."))
:message
"Get-utf-8-character Invalid byte 0x~X inside a character."))
(ldb (byte 6 0) byte)))
(case group-size
(1 (next-byte))
Expand All @@ -157,16 +158,18 @@ the string it encodes."
:with array-position :of-type fixnum = start
:with string-position :of-type fixnum = 0
:while (< array-position end)
:do (let* ((char (elt bytes array-position))
(current-group (utf-8-group-size char)))
(when (> (+ current-group array-position) end)
(error 'utf-8-decoding-error
:message "Unfinished character at end of byte array."))
(setf (char buffer string-position)
(code-char (get-utf-8-character bytes current-group
array-position)))
(incf string-position 1)
(incf array-position current-group))
:do
(let* ((char (elt bytes array-position))
(current-group (utf-8-group-size char)))
(when (> (+ current-group array-position) end)
(error 'utf-8-decoding-error
:message
"utf-8-bytes-to-string. Unfinished character at end of byte array."))
(setf (char buffer string-position)
(code-char (get-utf-8-character bytes current-group
array-position)))
(incf string-position 1)
(incf array-position current-group))
:finally (return buffer)))

(defun read-utf-8-string (input &key null-terminated stop-at-eof
Expand Down Expand Up @@ -205,7 +208,7 @@ max amount of characters or bytes to read."
:for next-char = (read-byte input nil :eof)
:do (when (eq next-char :eof)
(error 'utf-8-decoding-error
:message "Unfinished character at end of input."))
:message "read-utf-8-string. Unfinished character at end of input."))
:do (setf (elt buffer i) next-char))
(vector-push-extend (code-char (get-utf-8-character
buffer current-group))
Expand Down
121 changes: 116 additions & 5 deletions doc/dao-classes.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 722bf60

Please sign in to comment.