dyna

2017-05-16

Dyna

Build Status Quicklisp dist

Dyna is an AWS DynamoDB ORM for Common Lisp.

Usage

(defvar *dyna* (make-dyna :credentials (cons (asdf::getenv "AWS_ACCESS_KEY")
                                             (asdf::getenv "AWS_SECRET_KEY"))
                 :region "ap-northeast-1"))
 
(defclass thread ()
  ((forum-name :key-type :hash
               :attr-name "ForumName"
               :initarg :forum-name
               :accessor thread-forum-name)
   (subject :key-type :range
            :attr-name "Subject"
            :initarg :subject
            :accessor thread-subject))
  (:dyna *dyna*)
  (:table-name "Thread")
  (:metaclass <dyna-table-class>))

(migrate-dyna-teble 'thread)
;; => T

(save-dyna (make-instance 'thread :forum-name "Amazon DynamoDB"
                                  :subject "Really useful"))
;; => T

(find-dyna 'thread "Amazon DynamoDB" "Really useful")
;; => #<THREAD :forum-name "Amazon DynamoDB" :subject "Really useful">



;;; The operations below is the samples of Low Level API.

(fetch (dyna-credentials *dyna*) "local" "ListTables" "{}")
;; => #(...)

(put-item *dyna* :table-name "aliens"
                 :item (("Name" . "LispAlien") ("Feature" . "They talk Lisp.")))
;; => T

(get-item *dyna* :table-name "aliens" :key (("Name" . "LispAlien"))))
;; => (("Name" . "LispAlien") ("Feature" . "They talk Lisp."))

Installasion

(ql:quickload :dyna)

API

dyna

(make-dyna :credentials (cons "access-key" "secret-key")
           :region "ap-northeast-1")
  • dyna object is a object for setting up.
  • make-dyna creates dyna object.
  • :credentials is a dotted pair of AccessKey and SecretKey.
  • :region is a region of your DynamoDB.
  • If you want to access you local DynamoDB Local,
    you can setup :region "local" and (setf *local-port* 8000).

(defclass thread ()
  ((forum-name :key-type :hash
               :attr-name "ForumName"
               :attr-type :S
               :initarg :forum-name
               :accessor thread-forum-name)
   (subject :key-type :range
            :attr-name "Subject"
            :attr-type :S
            :initarg :subject
            :accessor thread-subject)
   (owner :attr-name "Owner"
          :attr-type :S
          :initarg :owner
          :accessor thread-owner)
   (last-post-date-time :attr-name "LastPostDateTime"
                        :attr-type :S
                        :initarg :last-post-date-time
                        :accessor thread-last-post-date-time))
  (:dyna *dyna*)
  (:table-name "Thread")
  (:throughput (:read 1 :wirte 1)
  (:lsi lat-post-date-time)
  (:gsi (:hash owner :read 5 :write 5))
  (:metaclass <dyna-table-class>))

;; Simpler Style

(defclass thread ()
  ((forum-name :key-type :hash
               :attr-type :S
               :initarg :forum-name
               :accessor thread-forum-name)
   (subject :key-type :range
            :attr-type :S
            :initarg :subject
            :accessor thread-subject))
  (:dyna *dyna*)
  (:metaclass <dyna-table-class>))
  • You can create class haveing as :metaclass.
  • :dyna can take dyna object.
  • :table-name can take table name of DynamoDB's table. (Optional)
  • :throughput is the ProvisionedThroughput of the table. (Optional)
  • You can create table without :throughput,
    then the first value of ProvisionedThroughput will be *default-throughput*,
    and you can adjust ProvisionedThroughput with AWS Console.
  • :lsi is columns of LocalSecondaryIndexes.
  • :gsi is columns of GlobalSecondaryIndexes.
  • You can create :gsi without :read nor :write,
    then the first value of ProvisionedThroughput in GlobalSecondaryIndexes will be
    *default-throughput*, and you can adjust ProvisionedThroughput with AWS Console.
  • :key-type in slot should be :hash or :range and is the same as DynamoDB's table.
  • :attr-name in slot is AttributeName of Item in DynamoDB's table. (Optional)
  • :attr-type in slot is AttributeType of Item in DynamoDB's table. (Optional)
  • You must attach :attr-type with Attributes used in Indexes.

create-dyna-table

(create-dyna-table 'thread)
;; => T
  • can return T if the table is successfully created.

update-dyna-table

(defclass thread ()
  ((forum-name :key-type :hash
               :attr-name "ForumName"
               :attr-type :S
               :initarg :forum-name
               :accessor thread-forum-name)
   (subject :key-type :range
            :attr-name "Subject"
            :attr-type :S
            :initarg :subject
            :accessor thread-subject))
  (:dyna *dyna*)
  (:table-name "Thread")
  (:throughput (:read 10 :wirte 10)
  (:metaclass <dyna-table-class>))
(update-dyna-table 'thread)
;; => T

(update-dyna-table 'thread)
;; => NIL
  • can return T if the table is successfully updated.
  • can return NIL if the table has no changs.

migrate-dyna-table

(migrate-dyna-table 'thread)
=> T
  • can create the table if the table doesn't exist.
  • can update the table if the table definitions are chagned.
  • can return NIL if the table has no changes.

find-dyna

(find-dyna 'thread "Amazon DynamoDB" "Really useful")
;; => #<THREAD :forum-name "Amazon DynamoDB" :subject "Really useful">
  • can return a object if matching Item exists.

select-dyna

(select-dyna 'thread)
;; => (#<THREAD > <#THREAD >)

(selet-dyna 'thread (where (:= :forum-name "Amazon DynamoDB")))
;; => (#<THREAD >)

(selet-dyna 'thread (where (:or (:= :forum-name "Amazon S3")
                                (:= :forum-name "Amazon DynamoDB"))))
;; => (#<THREAD > #<THREAD >)

(selet-dyna 'thread (where (:in :forum-name '("Amazon S3" "Amazon DynamoDB"))))
;; => (#<THREAD > #<THREAD >)

(selet-dyna 'thread (where (:in :forum-name '("Amazon S3" "Amazon DynamoDB")))
                    :limit 1)
;; => (#<THREAD >)

(selet-dyna 'thread (where (:in :forum-name '("Amazon S3" "Amazon DynamoDB")))
                    (limit 1))
;; => (#<THREAD >)

(select-dyna 'thread :segments 4)
;; => (#<THREAD > <#THREAD >)
  • returns the list of objects.
  • can handle Extended Where Clause of SxQL.
  • can handle LastEvaluatedKey in the response.
  • :limit can restrict the number of results.
  • can handle Limit Clause of SxQL.
  • :segments can make scan request divided.

save-dyna

(save-dyna (make-instance 'thread :forum-name "Amazon DynamoDB"
                                  :subject "Really useful"))
;; =>
  • can return T if the object is successfully saved.

Low Level API

Most Low Level API return multiple values, the formaer is formatted result, and the latter is raw result.

fetch

(fetch (cons "access-key" "secret-key") "ap-northeast-1" "ListTables" "{}")
;; => #(...)
  • returns raw octets of reponse.

batch-get-item

(batch-get-item dyna :request-items '(("Forum" . (("Keys" . ((("Id" . 1))
                                                             (("Id" . 2))))
                                                  ("ProjectionExpression" . "Id, Title, Author")))
                                      ("Thread" . (("Keys" . ((("ForumName" . "Amazon DynamoDB")
                                                               ("Subject" . "Concurrent reads"))))
                                                  ("AttributesToGet" . "ForumName, Subject"))))
                     :return-consumed-capacity "TOTAL")))
;; => (("Forum" (("Id" . 1) ("Title" . "Enjoy Lisp") ("Author" . "Rudolph-Miller"))
;;              (("Id" . 2) ("Title" . "Sophisticated Programming Language") ("Author" . "Lisp-Alien")))
;;     ("Thread" (("ForumName" . "Amazon DynamoDB") ("Subject" . "Concurrent reads"))))
  • returns the list of alists.
  • Support
    • :request-items
    • :return-consumed-capacity

batch-write-item

(batch-write-item dyna :request-items '(("Forum" . ((("PutRequest" . (("Item" . (("Name" . "Amazon DynamoDB")
                                                                                 ("Category" . "Amazon Web Services"))))))
                                                    (("PutRequest" . (("Item" . (("Name" . "Amazon RDS")
                                                                                 ("Category" . "Amazon Web Services"))))))))
                                        ("Thread" . ((("PutRequest" . (("Item" . (("ForumName" . "Amazon DynamoDB")
                                                                                  ("Subject" . "Concurrent reads")))))))))
                       :return-consumed-capacity "TOTAL")
;; => T
  • returns t if the operation succeeded.
  • Support
    • :request-items
    • :return-consumed-capacity
    • :return-item-collection-metrics

create-table

(create-table dyna :table-name "Thread"
                   :key-schema '((("AttributeName" . "ForumName") ("KeyType" . "HASH"))
                                 (("AttributeName" . "Subject") ("KeyType" . "RANGE")))
                   :attribute-definitions '((("AttributeName" . "ForumName") ("AttributeType" . "S"))
                                            (("AttributeName" . "Subject") ("AttributeType" . "S"))
                                            (("AttributeName" . "LastPostDateTime") ("AttributeType" . "S")))
                   :local-secondary-indexes '((("IndexName" . "LastPostIndex")
                                               ("KeySchema" . ((("AttributeName" . "ForumName")
                                                                ("KeyType" . "HASH"))
                                                               (("AttributeName" . "LastPostDateTime")
                                                                ("KeyType" . "RANGE"))))
                                               ("Projection" . (("ProjectionType" . "KEYS_ONLY")))))
                   :provisioned-throughput '(("ReadCapacityUnits" . 5)
                                             ("WriteCapacityUnits" . 5)))
;; => T
  • returns t if the operation succeeded.
  • Support
    • :table-name
    • :attribute-definitions
    • :key-schema
    • :global-secondary-indexes
    • :local-secondary-indexes
    • :provisioned-throughput

delete-item

(delete-item dyna :table-name "Thread"
                  :key '(("ForumName" . "Amazon DynamoDB"))
                  :condition-expression "attribute_not_exists(Replies)"
                  :return-values "ALL_OLD")
;; => T
  • returns t if the operation succeeded.
  • Support
    • :table-name
    • :key
    • :condition-expression
    • :return-values
    • :expression-attribute-names
    • :expression-attribute-values
    • :return-consumed-capacity
    • :return-item-collection-metrics

delete-table

(delete-table dyna :table-name "Thread")
;; => T
  • returns t if the operation succeeded.
  • Support
    • :table-name

describe-table

(describe-table dyna :table-name "Thread")
  • returns the jsown object of table description.
  • Support
    • :table-name

get-item

(get-item dyna :table-name "Thread"
               :key '(("Tags" . ("Multiple Items" "HelpMe")))
               :consistent-read t
               :return-consumed-capacity "TOTAL")
;; => (("Tags" "Multiple Items" "HelpMe") ("ForumName" . "Amazon DynamoDB"))
  • returns the alist of item.
  • Support
    • :table-name
    • :key
    • :projection-expression
    • :consistent-read
    • :return-consumed-capacity
    • :expression-attribute-names

list-tables

(list-tables-content dyna)
;; => ("Thread")
  • returns the list of table names.
  • Support
    • :exclusive-start-table-name
    • :limit

put-item

(put-item dyna :table-name "Thread"
                :item '(("Tags" . ("Multiple Items" "HelpMe"))
                        ("ForumName" . "Amazon DynamoDB"))
                :condition-expression "ForumName <> :f and Subject <> :s"
                :expression-attribute-values '((":f" . "Amazon DynamoDB")
                                               (":s" . "update multiple items")))
;; => T
  • returns t if the operation succeeded.
  • Support
    • :table-name
    • :item
    • :on-expression
    • :expression-attribute-values
    • :expression-attribute-names
    • :return-consumed-capacity
    • :return-item-collection-metrics
    • :return-values

query

(query dyna
       :table-name "Thread"
       :select "SPECIFIC_ATTRIBUTES"
       :attributes-to-get '("ForumName" "Subject")
       :limit 3
       :key-conditions '(("ForumName" . (("AttributeValueList" . ("Amazon DynamoDB"))
                                         ("ComparisonOperator" . "EQ"))))
       :return-consumed-capacity "TOTAL")
;; => ((("ForunName" . "Amazon DynamoDB") ("Subject" . "Concurrent reads")))
  • returns list of alists.
  • Support
    • :table-name
    • :key-conditions
    • :return-consumed-capacity
    • :attributes-to-get
    • :index-name
    • :select
    • :limit
    • :consistent-read
    • :conditional-operator
    • :exclusive-start-key
    • :expression-attribute-values
    • :expression-attribute-names
    • :filter-expression
    • :projection-expression
    • :query-filter
    • :scan-index-forward

scan

(scan dyna :table-name "Thread"
           :projection-expression "ForumName,Subject"
           :limit 3
           :filter-expression "Replies > :num"
           :expression-attribute-values '((":num" . 10))
           :scan-index-forward t
           :return-consumed-capacity "TOTAL")
;; => ((("ForunName" . "Amazon DynamoDB") ("Subject" . "Concurrent reads")))
  • returns list of alists.
  • Support
    • :table-name
    • :key-conditions
    • :return-consumed-capacity
    • :attributes-to-get
    • :index-name
    • :select
    • :limit
    • :consistent-read
    • :conditional-operator
    • :exclusive-start-key
    • :expression-attribute-values
    • :expression-attribute-names
    • :filter-expression
    • :projection-expression
    • :scan-filter
    • :scan-index-forward
    • :segment
    • :total-segments

update-item

(update-item dyna :table-name "Thread"
                  :key '(("ForumName" . "Amazon DynamoDB"))
                  :update-expression "set Replies = Replies + :num"
                  :expression-attribute-values '((":num" . 1))
                  :return-values "NONE")
;; => T
  • returns t if the operation succeeded.
  • Support
    • :table-name
    • :key
    • :update-expression
    • :expression-attribute-names
    • :expression-attribute-values
    • :return-values
    • :return-consumed-capacity
    • :return-item-collection-metrics

update-table

(update-table dyna :table-name "Thread"
                   :attribute-definitions '((("AttributeName" . "ForumName")
                                            ("AttributeType" . "S"))
                                            (("AttributeName" . "Subject")
                                            ("AttributeType" . "S"))
                                            (("AttributeName" . "LastPostDateTime")
                                            ("AttributeType" . "S")))
                   :provisioned-throughput '(("ReadCapacityUnits" . 5)
                                             ("WriteCapacityUnits" . 5))
                   :global-secondary-index-updates '((("Create" . (("IndexName" . "LastPostIndex")
                                                                   ("KeySchema" . ((("AttributeName" . "Subject")
                                                                                    ("KeyType" . "HASH"))
                                                                                   (("AttributeName" . "LastPostDateTime")
                                                                                    ("KeyType" . "RANGE"))))
                                                                   ("Projection" .(("ProjectionType" . "ALL")))
                                                                   ("ProvisionedThroughput" . (("ReadCapacityUnits" . 5)
                                                                                    ("WriteCapacityUnits" . 5))))))))
;; => T
  • returns t if the operation succeeded.
  • Support
    • :table-name
    • :attribute-definitions
    • :provisioned-throughput
    • :global-secondary-index-updates

Extended Where Clause

  • You can use ordinary operators.
  • Extended operators are below. (:between, :begins-with, :contains, :list=, :list-in)
(where (:between :forum-name '("A" "Z")))
(where (:begins-with :forum-name "Amazon"))
(where (:contains :forum-name "RDS"))

;; If Attribute Type of "Tags" is "SS", you query := and :in with :list= and :list-in respectively.
(where (:list= :tags '("AWS" "Easy")))
(where (:list-in :tags '(("AWS" "Easy") ("Scalable"))))

See Also

  • SxQL - A SQL generator.

Author

  • Rudolph-Miller (chopsticks.tk.ppfm@gmail.com)

Copyright (c) 2015 Rudolph-Miller (chopsticks.tk.ppfm@gmail.com)

License

Licensed under the MIT License.

Author
Rudolph-Miller
License
MIT