Fork me on GitHub
#beginners
<
2021-02-18
>
DG01:02:57

I think I found a difference in reduce with next.jdbc/plan and a regular collection. When using reduce with a regular collection I can make decisions based on the previous element if I want. With next.jdbc/plan it seems not possible. Since I’m a beginner I could be doing something wrong, of course. The following code work as expected:

(defn append [entry]
  (prn "Appending" entry))

(defn create-new [entry]
  (prn "Creating new" entry))

(defn not-same? [a b]
  (prn a "x" b)
  (not= a b))
  
(reduce
  (fn [prev-entry entry]
    (if (not-same? entry prev-entry)
      (create-new entry)
      (append entry))
    entry)
  nil
 [1 1 1 2 2 3 4 4 5])
The output is:
1 "x" nil
"Creating new" 1
1 "x" 1
"Appending" 1
1 "x" 1
"Appending" 1
2 "x" 1
"Creating new" 2
2 "x" 2
"Appending" 2
3 "x" 2
"Creating new" 3
4 "x" 3
"Creating new" 4
4 "x" 4
"Appending" 4
5 "x" 4
"Creating new" 5
The following (equivalent?) code with next.jdbc/plan doesn’t:
(defn append [entry]
  (prn "Appending" entry))

(defn create-new [entry]
  (prn "Creating new" entry))

(defn not-same? [a b]
  (prn a "x" b)
  (not= a b))

(reduce
  (fn [prev-entry entry]
    (if (not-same? entry prev-entry)
      (create-new entry)
      (append entry))
    entry)
  nil
  (jdbc/plan db ["SELECT 1 AS a UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 4 UNION ALL SELECT 5"]))
The output is:
{:a 1} "x" nil
"Creating new" {:a 1}
{:a 1} "x" {:a 1}
"Appending" {:a 1}
{:a 1} "x" {:a 1}
"Appending" {:a 1}
{:a 2} "x" {:a 2}
"Appending" {:a 2}
{:a 2} "x" {:a 2}
"Appending" {:a 2}
{:a 3} "x" {:a 3}
"Appending" {:a 3}
{:a 4} "x" {:a 4}
"Appending" {:a 4}
{:a 4} "x" {:a 4}
"Appending" {:a 4}
{:a 5} "x" {:a 5}
"Appending" {:a 5}
Seems like the IReduceInit protocol implemented by next.jdbc is always calling the reducer with the current element of the result-set. @seancorfield Hi, I believe you are the maintainer. Should I open a bug report for this or I’m just doing it wrong? Thanks!

seancorfield01:02:38

next.jdbc/plan provides a reducible abstraction over a mutable ResultSet object from Java. The ResultSet can only point at the current row during each invocation of the reducing function: the "previous" row is no longer available.

DG01:02:40

I know, the same applies with the first vector example, right?

seancorfield01:02:08

However, you are also using plan incorrectly by trying to use "whole rows": the intent of the abstraction is that you can get the value of specific columns without realizing the whole row. If you do want the whole row, you should call datafiable-row on it to realize it into a Clojure data structure.

seancorfield01:02:55

(and in fact if you realize each row using datafiable-row then your reduce will actually work because you're dealing with just Clojure data at that point and not the (mutable) ResultSet object)

DG01:02:27

Oh, I understand now. That’s the issue then. ResultSet is mutable and I’m reducing the same object 😐

seancorfield01:02:10

Right. plan has a pretty specific use case -- and the reduction takes care of opening and closing a connection to the database (or connection pool).

DG01:02:22

Thank you!

seancorfield01:02:39

If you have SQL-specific Qs, or questions about next.jdbc, I'm more likely to see it in the #sql channel which is lower traffic than #beginners where I can easily miss something.

DG01:02:01

Oh good, didn’t know that. Will join, thank you once again.

DG01:02:21

Ah, I just made the code work with rs/datafiable-row

3
Matti Uusitalo07:02:42

I’m trying to (clojure.tools.namespace.repl/refresh) , but it fails when it tries to process some cljs stuff. I’m in the jvm repl. I suspect some cljc file doesn’t play nice. Is there a good way to track down what causes this?

Matti Uusitalo07:02:23

It is a clj & cljs project and I have started both repls in CIDER.

GGfpc12:02:11

What would you say is the best resource to learn parallel programming in clojure? I'm interested in both the primitive constructs of the language and core.async

noisesmith16:02:03

none of the clojure specific constructs for parallelism are "primitive", they build on the vm primitives to add some conveniences

noisesmith16:02:33

much of the design of the language is set up so that parallel code works as much like non parallel code as possible (eg. the way atoms / refs / agents are designed are various ways to simplify mutable values in parallel code)

noisesmith16:02:09

I wouldn't suggest ever using core.async to make your code parallel, it's a good tool for coordinating flows of values which may or may not happen in parallel as opposed to just async. using core.async to make code parallel rarely improves things in my experience, but once you have things that are already realized in undefined order it's good for simplifying that code

noisesmith16:02:37

regarding your actual question I don't know of any good resources, sorry - though this talk expands on some stuff about core.async https://www.youtube.com/watch?v=096pIlA3GDo

noisesmith17:02:57

rewatching, it's also a great lesson via example of refactoring ugly clojure code

Adrian Imanuel17:02:47

I'm trying to get a sheet title using this API https://github.com/SparkFund/google-apps-clj/blob/develop/src/google_apps_clj/google_sheets_v4.clj

(defn get-sheet-titles
  [service spreadsheet-id]
  "returns a list of [sheet-title sheet-id] tuples. It seems the order reflects
  the order of the tabs in google's interface, though I doubt this is anywhere
  guaranteed."
  (->> (get (get-spreadsheet-info service spreadsheet-id) "sheets")
       (map #(get % "properties"))
       (mapv (juxt #(get % "title") #(get % "sheetId")))))
this is the code i wrote
(ns asdf.core
  (:require [clojure.edn :as edn]
            [clojure.test :refer :all]
            [google-apps-clj.google-sheets-v4 :refer :all :as gsheets]))

(defn login
  []
  (gsheets/build-service ""
                       "_la3mykeysecretJ"))

(login)

(gsheets/get-sheet-titles
 "16qb3Zg4kdD62y7cf0oQ4TrRmq890V19fpnu7hmRpTeg")
but i got an error said :
Execution error (ArityException) at asdf.core/eval4230 (form-init16301128171610314250.clj:63).
Wrong number of args (1) passed to: google-apps-clj.google-sheets-v4/get-sheet-titles
where's the mistake?

Adrian Imanuel17:02:58

nvm, i found it, i must wrote this instead

(gsheets/get-sheet-titles
 (login)
 "16qb3Zg4kdD62y7cf0oQ4TrRmq890V19fpnu7hmRpTeg")

andy.fingerhut18:02:32

I have no idea if that is some kind of password or only some shorter-lived authentication string that no one else can take advantage of, but you might want to be careful about publishing such things to the world 🙂

Adrian Imanuel18:02:49

Thanks for reminding, yes & no worries i've modified the authentication & delete the authentication after posting

Adrian Imanuel18:02:40

@U0CMVHBL2 I'd like to ask if you can help me, how to write Sheet1!B9 using this guidance below?

"sheet-ranges is a seq of strings, using the A1 syntax, eg [\"Sheet!A1:Z9\"]
   Returns a vector of tables in corresponding to sheet-ranges.  Only one
   sheet (tab) can be specified per batch, due to a quirk of Google's API as far
   as we can tell."

andy.fingerhut18:02:19

So in Clojure source code, [ "a" "b" ] is a vector containing two strings.

andy.fingerhut18:02:40

What you show above looks like it was copied and pasted from a Clojure program source file?

andy.fingerhut18:02:20

I guess that because the backslashes in front of each double-quote character (") are needed inside of a Clojure string if you want a double-quote to be a character in the string, i.e. you don't want that double-quote to end the string

Adrian Imanuel18:02:46

(defn get-cell-values
  "sheet-ranges is a seq of strings, using the A1 syntax, eg [\"Sheet!A1:Z9\"]
   Returns a vector of tables in corresponding to sheet-ranges.  Only one
   sheet (tab) can be specified per batch, due to a quirk of Google's API as far
   as we can tell."
  [^Sheets service spreadsheet-id sheet-ranges]
  (let [tables (get-cells service spreadsheet-id sheet-ranges)]
    (mapv (partial mapv (partial mapv cell->clj)) tables)))
this is the complete,

andy.fingerhut18:02:55

So if you look at the documentation as it would show up normally, it would be saying ["Sheet!A1:Z3"], which is a vector containing 1 string

andy.fingerhut18:02:46

The earlier English docs say "a seq of strings", where "seq" is short for "sequence". A Clojure vector is one kind of thing that will be treated as a sequence, as will a list and a few other Clojure data structures.

andy.fingerhut18:02:06

I've never used this API, by the way -- I'm just mentioning a few facts about Clojure that you might already know.

andy.fingerhut18:02:05

If you are trying to get only the value of 1 cell in the sheet, I would attempt to use something like ["Sheet!B9"] , a vector of 1 string, and see if it works.

Adrian Imanuel18:02:47

Ahhh yes it works, we need to define the exact name of the sheet though... thanks a lot!

(gsheets/get-cell-values
 (login)
 "16qb3Zg4kthisworksq890V19fpnu7hmRpTeg"
 ["Sheet1!B9"])

Erik Thorselius20:02:41

If I want a small, local and easy to use database. Is sqlite or h2 the way to go or is there anything else that fits well with clojure?

hiredman20:02:04

I am partial to apache derby

hiredman20:02:29

Which was even bundled with some jdks at one point as "javadb"

hiredman20:02:21

Using sqlite from the jvm usually ends up either needing to load a native library, or using native code compiled to arm asm, and then the arm asm turned into jvm bytecode

hiredman20:02:44

So tricky and not always portable

Erik Thorselius20:02:21

Ok, cool! I will check out derby

hiredman20:02:58

The few times I have tried h2 I have run afoul of concurrency issues (this might have been a configuration issue)

borkdude21:02:39

hsqldb (aka HyperSQL) is also an option. Works well with native-image too