2015-10-20 22:22 GMT+02:00 Dane Foster <studdu...@gmail.com>: > Here is the updated version w/ the feedback incorporated. I'm going to > install PostgreSQL 9.6 from source this weekend so I can start > testing/debugging. Does anyone here have any experience using the pgAdmin > debugger recently? I ask because it seems a little dated (September 26, > 2008). > > > Thanks, > > Dane > > /** > * Returns the status of a coupon or voucher. > * @param _code The discount code. > * @return NULL if the discount does not exist otherwise a composite type > (see return > * type declaration below). > > * > * Voucher codes have the following properties: > * type - The type of discount (voucher, giftcert). > * > * status - The status of the voucher. The valid values are: > * void - The voucher has been voided. > * > * expired - The voucher has expired. > * > * inactive - The gift certificate has not been sent yet. > * > * ok - The voucher has been activated, has not expired, > and has a > * current value greater than zero. > * > * date - The expiration or activation or void date of the voucher in > a reader > * friendly format. > * > * datetime - The expiration or activation or void date of the gift > certificate in > * YYYY-MM-DD HH:MM:SS format. > * > * value - The current value of the voucher. > * > * The mandatory properties are type and status. The presence of the other > properties > * are dependent on the value of status. > > > ************************************************************************************ > * Coupon codes can provide the following additional parameters that are > used to > * determine if an order meets a coupon's minimum requirements. > * @param int seats The number of seats in the user's order. > > * @param numeric subtotal The order's subtotal. > * > * Coupon codes have the following properties: > * type - The type of discount (coupon). > * > * status - The status of the coupon code. The valid values are: > * void - The coupon has been voided. > * > * expired - The coupon has expired. > * > * inactive - The coupon has not been activated yet. > * > * min - The minimum seats or dollar amount requirement > has not been > * met. > * > * ok - The coupon can be used. > * > * min - The minimum seats or dollar amount requirement. The value of > this > * property is either an unsigned integer or dollar amount > string w/ the > * dollar sign. > * > * date - The expiration or activation or void date of the coupon in a > reader > * friendly format. > * > * datetime - The expiration or activation or void date of the coupon in > YYYY-MM-DD > * HH:MM:SS format. > * > * value - The current value of the coupon as a string. The value of > this property > * is either an unsigned integer w/ a percent symbol or dollar > amount > * string w/ the dollar sign. > */ > CREATE OR REPLACE FUNCTION check_discount_code( > _code public.CITXT70, > VARIADIC cpnxtra NUMERIC[] > ) > RETURNS TABLE ( > type TEXT, > status TEXT, > date TEXT, > datetime TIMESTAMPTZ, > value TEXT, > min TEXT > ) AS $$ >
it is wrong, you are return composite, not SETOF composites (table). Use OUT parameters instead or declared custom type CREATE TYPE foo_result_type AS (a int, b int, c int); CREATE OR REPLACE FUNCTION foo(..) RETURNS foo_result_type AS $$ $$ > DECLARE > discount RECORD; > BEGIN > > SELECT > ok, > created, > expires, > modified, > effective_date, > -- The minimum quantity or dollar amount required to use the coupon. > COALESCE( > lower(qty_range), > '$' || to_char(lower(amount_range), '999999999999999D99') > ) AS min, > CASE type::TEXT > WHEN 'voucher' > THEN > CASE WHEN gd.code IS NOT NULL THEN 'giftcert' END > ELSE > type::TEXT > END AS type, > to_char(expires, 'Dy, MM Mon. YYYY') AS expd, > to_char(modified, 'Dy, MM Mon. YYYY') AS mdate, > to_char(effective_date, 'Dy, MM Mon. YYYY') AS edate, > -- The gift certificates remaining value or the coupon's discount > value as a > -- dollar amount or percent. > COALESCE( > value, > discount_rate || '%', > '$' || to_char(discount_amount, '999999999999999D99') > ) AS value, > -- Determines if the coupon has been used up. > CASE WHEN maxuse > 0 THEN maxuse - used <= 0 ELSE FALSE END AS maxuse, > effective_date > CURRENT_DATE AS notyet, > expires < CURRENT_DATE AS expired, > cpn.code IS NULL AS > danglingcoupon, > v.code IS NULL AS > danglingvoucher > INTO STRICT discount > FROM > discount_codes AS dc > LEFT JOIN coupons AS cpn USING (code) > LEFT JOIN vouchers AS v USING (code) > LEFT JOIN giftcerts_d AS gd USING (code) > WHERE > dc.code = _code; > > IF FOUND THEN > CASE discount.type > WHEN 'coupon' > THEN > -- This should NEVER happen! > IF discount.danglingcoupon > THEN > DELETE FROM discount_codes WHERE code = _code; > RAISE WARNING 'Removed dangling coupon code: %', _code; > ELSE > IF discount.maxuse OR NOT discount.ok > THEN > RETURN (discount.type, 'void'); > END IF; > > IF discount.expired > THEN > RETURN (discount.type, 'expired', discount.expd, > discount.expires); > END IF; > > IF discount.notyet > THEN > RETURN ( > discount.type, > 'inactive', > discount.edate, > discount.effective_date > ); > END IF; > /** > * Coupon codes can provide up to two additional parameters that > are used > * to determine if an order meets a coupon's minimum > requirements. > * > * int seats (i.e., cpnxtra[0]) The number of seats in the > user's order. > * numeric subtotal (i.e., cpnxtra[1]) The order's subtotal. > */ > IF 2 = array_length(cpnxtra, 1) > THEN > IF discount.min IS NOT NULL > THEN > -- @TODO - Test the regex to ensure it is escaped properly. > IF discount.min ~ '^\$' > THEN > IF right(discount.min, -1)::NUMERIC > cpnxtra[1]::NUMERIC > THEN > RETURN ( > discount.type, > 'min', > discount.edate, > discount.effective_date, > discount.value, > discount.min > ); > END IF; > ELSIF discount.min::INT > cpnxtra[0]::INT > THEN > RETURN ( > discount.type, > 'min', > discount.edate, > discount.effective_date, > discount.value, > discount.min > ); > END IF; > > RETURN ( > 'coupon', > 'ok', > discount.edate, > discount.effective_date, > discount.value, > discount.min > ); > END IF; > END IF; > > RETURN ( > 'coupon', > 'ok', > discount.edate, > discount.effective_date, > discount.value > ); > END IF; > ELSE > -- This should NEVER happen! > IF discount.danglingvoucher > THEN > DELETE FROM discount_codes WHERE code = _code; > RAISE WARNING 'Removed dangling voucher: %', _code; > ELSE > IF NOT discount.ok > THEN > RETURN (discount.type, 'void', discount.mdate, > discount.modified); > END IF; > > IF discount.expired > THEN > RETURN (discount.type, 'expired', discount.expd, > discount.expires); > END IF; > > IF discount.notyet > THEN > RETURN ( > discount.type, > 'inactive', > discount.edate, > discount.effective_date, > to_char(discount.value, '999999999999999D99') > ); > END IF; > -- Please note that even though the gift certificate is valid we > return > -- the expiration date information. This is because the data is > shown to > -- the user to inform them of when their gift certificate > expires. > IF discount.value > 0 > THEN > RETURN ( > discount.type, > 'ok', > discount.expd, > discount.expires, > to_char(discount.value, '999999999999999D99') > ); > END IF; > > RETURN (discount.type, 'depleted'); > END IF; > END CASE; > END IF; > > RETURN NULL; > > END; > $$ LANGUAGE plpgsql STRICT; > > this function is pretty long, you can divide it - to two maybe three parts - first - taking data, second - checking, > > Dane > > On Tue, Oct 20, 2015 at 1:45 PM, Dane Foster <studdu...@gmail.com> wrote: > >> On Tue, Oct 20, 2015 at 12:43 PM, Merlin Moncure <mmonc...@gmail.com> >> wrote: >> >>> On Tue, Oct 20, 2015 at 9:45 AM, Dane Foster <studdu...@gmail.com> >>> wrote: >>> > Hello, >>> > >>> > I'm in the very very very very early stages of migrating a MySQL/PHP >>> app to >>> > PostgreSQL/PHP/Lua. Because we are moving to PostgreSQL one of the >>> [many] >>> > things I intend to change is to move ALL the SQL code/logic out of the >>> > application layer and into the database where it belongs. So after >>> months of >>> > reading the [fine] PostgreSQL manual my first experiment is to port >>> some >>> > PHP/SQL code to a PostgreSQL function. >>> > >>> > At this stage the function is a purely academic exercise because like >>> I said >>> > before it's early days so no data has been migrated yet so I don't >>> have data >>> > to test it against. My reason for sharing at such an early stage is >>> because >>> > all I've done so far is read the [fine] manual and I'd like to know if >>> I've >>> > groked at least some of the material. >>> > >>> > I would appreciate any feedback you can provide. I am particularly >>> > interested in learning about the most efficient way to do things in >>> PL/pgSQL >>> > because I would hate for the first iteration of the new version of the >>> app >>> > to be slower than the old version. >>> > >>> > Thank you for your consideration, >>> >>> This is beautiful code. It in fact is an for all intents and purposes >>> an exact replica of my personal style. >>> >>> Some notes: >>> *) I agree with Pavel; better to return specific columns if the result >>> is well defined (mark them in the argument list with OUT and I tend to >>> not prefix underscore them in that case). The caller can always do a >>> json production if necessary, or you can wrap the function. >>> >>> Some other minor suggestions: >>> *) I tend to prefer format() to || concatenation in ALL usage these >>> days. It's more readable and tends to give better handling of NULL >>> strings by default. >>> >>> *) this login should really be documented in line >>> IF 2 = array_length(cpnxtra, 1) >>> THEN >>> >>> *) I avoid all right justified code (spaced out AS x, AS y, etc). I >>> understand the perceived readability improvements but none of them are >>> worth the cascading edits when variables get longer. >>> >>> *) let's compare notes on your doxygen style code markup. I've been >>> trouble finding a good robust tool that does exactly what I want, >>> curious if you did better. >>> >>> *) FYI, since you're obviously not using pgadmin, I use 'Sublime Text >>> 3' for my code editor. I've significantly enhanced it to support >>> various postgresqlisms, so if you're maintaining code in a codebase, >>> you have reasonable support for 'jump to definition' and things like >>> that. >>> >>> merlin >>> >> >> Thank you Pavel and Merlin for the feedback. I'm delighted that my first >> PL/pgSQL function wasn't rubbish. I think the credit goes to the authors of >> the [fine] PostgreSQL manual. >> >> Pavel, I've taken your recommendation to heart but I'll need to do some >> more reading on composite types because I didn't realize they were on >> option in this context (i.e., the fields/columns aren't fixed). >> >> Merlin: >> I went w/ || on purpose because I want/need its NULL behavior. The >> relationship between the columns with which || is used is a binary >> (mutually exclusive) relationship. So they both can't be NULL nor NOT NULL. >> >> I understand that right justification is an issue of personal taste. For >> me SQL is such a verbose and dense language that I use the justification to >> help break it up into visually manageable chunks. In traditional >> programming languages we have curly braces and/or indentation to help us >> visually organize and parse the code. I try to use justification to the >> same effect. And since most code is read more frequently than it's written >> I think a little realigning is a small price to pay. >> >> I haven't investigated or encountered any doxygen processing tools. As a >> matter of fact I wasn't even aware that the commenting style that I used >> was called doxygen! Until recently I used to program in Java regularly >> (since the Java 1.1 days) so I have a tendency to bring that style of >> commenting w/ me to other languages. The version on display is a PHP'ified >> variation of JavaDoc which thanks to you I just learned is called doxygen. >> >> Like I said I'm an old Java hack and used to use IntelliJ/IDEA to sling >> Java. But even though I rarely code in Java anymore I continue to use IDEA >> for coding everything, except shell scripts. IDEA has support for "jump to >> definition" and (more importantly) renames across files (i.e., refactoring). >> >> Thanks again for the feedback it is truly appreciated. >> >> Regards, >> >> Dane >> >> >> >