Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A Modern Finagle-Postgresql Client

License

NotificationsYou must be signed in to change notification settings

finagle/roc

Repository files navigation

Roc Logo

roc is a modernFinaglePostgresqlClient. What's modern? A Client relying on a6.x + version of Finagle.

Badges

PyPIMaven CentralCodecov branchCircleCI branchGitter

tl;dr

Roc is published toMaven Central, so for the latest stable version add the following to your build:

libraryDependencies++=Seq("com.github.finagle"%%"roc-core"%"0.0.4","com.github.finagle"%%"roc-types"%"0.0.4")

Roc is under heavy development, so to stay up to with the latestSNAPSHOT version add the following to your build instead:

resolvers+=Resolver.sonatypeRepo("snapshots")libraryDependencies++=Seq("com.github.finagle"%%"roc-core"%"0.0.5-SNAPSHOT" changing())

Opensbt console and insert the following

scala>:pasteimportcom.twitter.util.Awaitimportroc.Postgresqlimportroc.postgresql.{Request,Row}valclient=Postgresql.client  .withUserAndPasswd("username","password")  .withDatabase("database")  .newRichClient("inet!localhost:5432")valreq=newRequest("SELECT * FROM STATES;")valresult=Await.result(client.query(req))result: roc.postgresql.Result=Result(Text('id,23,1)Text('name,25,Alabama)Text('abbrv,1043,AL),Text('id,23,2)...)

Let's turn aResult into all 50State(s).

importjava.time.ZonedDateTimeimportroc.types.decoders._caseclassState(id:Int,name:String,abbrv:String,insertedAt:ZonedDateTime)valrow2State: (Row)=> (State)= (row:Row)=> {valid= row.get('id).as[Int]valname= row.get('name).as[String]valabbrv= row.get('abbrv).as[String]valinsertedAt= row.get('inserted_at).as[TimestampWithTZ]State(id, name, abbrv, insertedAt)}valstates= result.map(row2State).toListstates:List[State]=List(State(1,Alabama,AL,2016-05-10T11:59:13.879709-05:00),State(2,Alaska,AK,2016-05-10T11:59:20.974995-05:00))

If you're into Scaladocs ( I am ), they can be foundhere.

Tell me about Result

The most important type in Roc isResult, the type returned after a Postgresql query is executed. Result implementsIterable so that it can be viewed as a collection ofRows.The two additional members ofResult are:

valresult= client.query(newRequest("SELECT * FROM FOO;"))result.columns// all column information returned from the Requestresult.completedCommand// a String representation of what happend

Result.completedCommand

  • For an INSERT command, the tag is INSERT oid rows, where rows is the number of rows inserted. oid is the object ID of the inserted row if rows is 1 and the target table has OIDs; otherwise oid is 0.
  • For a DELETE command, the tag is DELETE rows where rows is the number of rows deleted.
  • For an UPDATE command, the tag is UPDATE rows where rows is the number of rows updated.
  • For a SELECT or CREATE TABLE AS command, the tag is SELECT rows where rows is the number of rows retrieved.
  • For a MOVE command, the tag is MOVE rows where rows is the number of rows the cursor's position has been changed by.
  • For a FETCH command, the tag is FETCH rows where rows is the number of rows that have been retrieved from the cursor.

For anUPDATE orINSERT command, aResult will have a length of0 and no column information, but will always return acompletedCommand.From Postgresql's perspective, the fact that the query returns without giving an error is evidence that the command completed successfully, andRoc will adhere to their style, not theJDBC style.

Row

ARow holds a non-zero number ofElements.AnElement is the actual value returned at[row][column].For example, if we were to execute the following:

scala>valreq=newRequest("SELECT COUNT(*) FROM STATES;")scala>valresult=Await.result(client.query(req))result: roc.postgresql.Result=Result(Text('count,20,50))

we are given aResult with oneColumn (with the name of'count, a FormatCode of Text, and OID of 20), and oneRow.To retrieve a value out of thatRow, we do the following:

scala>valhead= result.head// let's just get the first rowscala>valcount= head.get('count)count: roc.postgresql.Element=Text('count,20,50)

Wait, what exactly is anElement?

Elements

roc-core has an extremely minimal design philosophy. That includes the decoding of actual data. In other words,we'll decode the bytes into the correct format, we'll tell you what that format is, but it's up to you (or another roc module)to go any further.Postgreql returns data in 3 possible formats:

  1. UTF-8 Text
  2. Binary Format (typically Big Endian)
  3. No data is returned for that column ( mean it is a NULL value )

roc-core will decode this data into the given format, but goes no further in the process - it is up to core clients to decide how to procede.Going back to the example above, we see:

scala>valcount= head.get('count)count: roc.postgresql.Element=Text('count,20,50)

This means that Postgresql has returned a column name'count, in a String format, with a Postgresql Type ofLong.String encodings are typically preferred, and almost universally the case unless the column returned is binary data,or if aFETCH command is used to return aCURSOR.AnElement has 3 sub-types

  1. Text
  2. Binary
  3. NULL

Yes, we've deliberately introduced a specificNULL type into the system. This allows clients to handleNULL cases in whatever way they see fit.

To get a value out of anElement, you have several options:

scala>valcount= head.get('count)count: roc.postgresql.Element=Text('count,20,50)scala> count.asStringres4:String=50scala> count.asBytesroc.postgresql.failures$UnsupportedDecodingFailure:AttemptedBinary decoding ofString column.

If you attempt to get the String of a Binary element, you'll get anotherUnsupportedDecodingFailure.The two attempts above are short cuts to getting values. The preferred method involves a fold:

deffold[A](fa:String=>A,fb:Array[Byte]=>A,fc: ()=>A):A=thismatch {caseText(_, _, value)=> fa(value)caseBinary(_, _, value)=> fb(value)caseNull(_, _)=> fc()}

This allows you to handleNULL values in any way you see fit, and makes the decode process typesafe.BothasString andasBytes callfold under the covers.

Finally, there is the ubiquitous parsing / decoding methodas[A]:

defas[A](implicitf:ElementDecoder[A]):A= fold(f.textDecoder, f.binaryDecoder, f.nullDecoder)

AnElementDecoder is a TypeClass to allow custom decoding in a more syntax friendly way. See theScaladocsor gitter for more information.

decoders

Theroc-types project defines type aliases fromPostgresql => Scala, and includesElementDecoder instances for those types. The current types include

  • smallint => Short
  • int => Int
  • bigint => Long
  • real => Float
  • double precision => Double
  • char => Char (Note this is a C-Style understanding of a Char, not a UTF Rune)
  • text/CHARACTER VARYING => String
  • bool => Boolean
  • JSON/JSONB => Json (via Jawn)
  • Date => Date = java.time.LocalDate
  • Time => Time = java.time.LocalTime
  • TIME WITH TIME ZONE => TimestampWithTZ = java.time.ZonedDateTime
  • NULL => Option

Optional Decoders

To decode a column that may be NULL, clients should simply use anOption[A] decoder, whereA = COLUMN TYPE.Let's add apopulation column to ourstates table, of typeint.

caseclassState(id:Int,name:String,abbrv:String,population:Option[Int],insertedAt:ZonedDateTime)valrow2State: (Row)=> (State)= (row:Row)=> {valid= row.get('id).as[Int]valname= row.get('name).as[String]valabbrv= row.get('abbrv).as[String]valpopulation= row.get('population).as[Int]valinsertedAt= row.get('inserted_at).as[TimestampWithTZ]State(id, name, abbrv, population, insertedAt)}valstate= result.map(row2State).toList.headstates:State=State(1,Alabama,AL,None,2016-05-10T12:46:59.998788-05:00)

As the type of column should be known at compile time,roc-types throws anNullDecodedFailure(TYPE)with a helpful error message if you attempt to decode aNULL type:

scala>valrow= result.headrow: roc.postgresql.Row=Text('inserted_at,1184,2016-05-1012:46:59.998788-05)Null('population,23)Text('abbrv,1043,AL)Text('name,25,Alabama)Text('id,23,1)scala>valpopulation= row.get('population).as[Int]roc.types.failures$NullDecodedFailure:ANULL value was decodedfortypeINT.Hint: use theOption[INT] decoder, or ensure thatPostgres cannotreturnNULLfor the requested value.

Design Philosophy

The desire ofcore is to be as minimal as possible. In practice, that means mapping a Finagle Service over Postgresql with as little bedazzling as possible.The Postgresql Client will be very minimalistic ( currently just one method,def query(Request): Future[Result]), and the aim is forResult to do as little as possible.Additional future modules may provide additional functionality.

Motivation

The currentfinagle-postgres was developed preFinagle 6.x and does not use modern finagle abstractions. These are core enough to require what amounts to a complete rewrite of the driver.

What's in a name?

roc (Rokh orRukh) is named after the Persian mythological bird of preyRoc,which was based in part on the now extinctelephant bird. Yes, that means we've hit the rare 4 point play with the name

GoalStatus
short name✔️
obligatory mythological reference✔️
bird reference for Twitter✔️
elephant reference for Postgresql✔️

High fives for everyone!Teenage Mutant Ninja Turtles High Fives

Contributors and Participation

roc is currently maintained byJeffrey Davis.

The roc project supports theTypelevelcode of conduct and wantsall of its channels (GitHub, gitter, etc.) to be welcoming environments for everyone.

License

Licensed under theBSD-3-Clause(Revised BSD Liscense); you may not use this software except in compliance with the License.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


[8]ページ先頭

©2009-2025 Movatter.jp