Creating my own implementation of DBI::Iterator in perl - oracle

UPDATE:
Right now I am trying to take in to an array around 120M of rows. Reason I'm not doing this over UTL_file is because in our production server it requires oracle user access to write and move the written flat-file into our desired directory. Add in to the problem is that there are still a lot of text manipulations needed after extraction which I think is a job for Perl.
What I want to do right now is to write my own implementation of the DBI::Iterator and all other dependencies it may need; to be clear though, not necessarily all. (like some methods only of DBI, then just idb_rows... just to get this running since I cannot install modules)
Original Question:
Good day,
I'm relatively new to perl programming, A week ago I started receiving out of memory messages again in perl. I was able to get around this before by switching to 64 bit perl.
I just found yesterday that the kernel of my production machine is not allowing me to use more than 4GB of memory (in my other production server I am able to load large amounts of data to memory)
Here are my specific restrictions in my production server
I am not allowed to install new modules of perl
I am allowed, in a way, to install them locally but I am having trouble
What I intend to do now is to recreate this module.
Iterator::DBI
I have no background of iterators. for the longest time I develop database extractions and ETL processes through the function below. It's my 1st time to encounter an out of memory error again after a year and a half of using the function below.
sub myDBI
{
my ($filename) = #_;
my $query = "";
unless(open(FILE,$filename))
{
Logger("[ ERR ] unable to open $SETTINGS{SQL_TRIGGER}\n");
print
die;
}
my #result=`sqlplus -S $SETTINGS{USER_NAME}/$SETTINGS{PASSWORD}\#$SETTINGS{DB_NAME} <<END
SET HEADING OFF
SET FEEDBACK OFF
SET SERVEROUTPUT ON
SET LINES 5000
SET COLSEP "||"
$query
/
`
;
#result;
}

You have several options:
If you have local::lib installed, you can install CPAN modules like Iterator::DBI to a user directory. You'll just need to set some environment variables to specify which directory to use.
export PERL_MB_OPT='--install_base /home/username/perl5'
export PERL_MM_OPT='INSTALL_BASE=/home/username/perl5'
export PERL5LIB='/home/username/perl5/lib/perl5/i386-linux:/home/username/perl5/lib/perl5'
export PATH="/home/username/perl5/bin:$PATH"
You actually don't need Iterator::DBI. That module just wraps an Iterator object around a DBI statement handle, which is an iterator itself. So you can just use DBI directly to connect to the database. (Note that either way, you'll connect to the database directly rather than going through sqlplus.)
use DBI;
my $dbh = DBI->connect(...);
my $sth = $dbh->prepare($sql_query);
$sth->execute(#params);
# iterate
while (my $row = $sth->fetchrow_arrayref) {
...
}
If you really want to use Iterator::DBI and you can't install the module, you can just copy the source code directly and put it in ./Iterator/DBI.pm relative to your application. But the problem with this is you'll need to get around the dependencies. To do that I would replace exceptions with a simple die or croak, and replace Iterator with a closure (see chapter 5 of Higher Order Perl for how to do this). This option looks quite difficult for a beginner Perl programmer.
If you really can't get DBI working, you can pipe the sqlplus output to a file and iterate through the file.

Related

Ruby: how to access group info from /var/db/group.db

RHEL/CentOS 8.x, Ruby 2.5.5, irb 0.9.6
We have a specialized set up that keeps user and group information in /usr/local/etc/[user|group] and a custom Makefile to add that information to /var/db/[group|passwd].db accordingly.
I am trying to get the list of groups a user belongs to, in Ruby, after login.
I'm relatively new to the language, and have just read the documentation for the Etc module but that seems to exclusively work with the /etc/ filesystem. Not outrageous tbh.
Is there an easy way to access the Berkley DB info in /var/db or am I going to have to iterate through the /usr/local/etc/group file to get this information?
I suspect that the documentation of that module is heavily simplified, heavily outdated, or both. I am almost 100% sure that Etc will use the OS-provided standard library functions instead of going off and parsing system files by itself. (Why would the Ruby developers write parsers for the configuration files of every single OS they support, instead of just calling a POSIX function?)
You can confirm this suspicion using strace.
If you look at how the Ruby Etc module is structured, it maps 1:1 to the POSIX functions:
Etc::gegtrent: gets the next group entry.
Etc::endgtrent: stops iterating the groups.
Etc::settrent: resets the iteration.
Here are the POSIX functions for comparison:
endgrent, getgrent, setgrent – group database entry functions
In my case, I am testing this on macOS (which already has specialized user handling), but furthermore, I tested it on my company laptop, which is macOS joined to a Microsoft ActiveDirectory Domain. My user account and its group aren't even mentioned in either /etc/passwd or /etc/group, and yet, I can easily read them with Etc. I am very sure that the Ruby developers did not implement ActiveDirector just to accommodate my personal weird use case, so it must use system functions.
So, if you have e.g. a uid and want to know what groups it belongs to, you need to first get its name, and then search the groups. Unfortunately, it looks like the provided iterator wrappers do not support the common idiom that not supplying the block returns an Enumerator, so you have to create one yourself.
require 'etc'
uid = 0 # for example
username = Etc.getpwuid(uid).name
groups = Etc.enum_for(:group).select {|group| group.mem.include?(username) }
p groups

How can I make my terminal text rainbow colored upon every boot up? (with lolcat)

I've been wondering how I could do some of these cool customization options for the terminal on a Mac and I came across lolcat.
However, I can't seem to find an answer as to how to add this into my bashrc (FYI: I'm using zsh now just in case that makes a difference as to which file to add my customizations in) I have tried what many others have suggested, which was just typing zsh | lolcat into the terminal to get rainbow output in the current session, but I was wondering how I can have this every time I start a terminal session.
Also, I'm not sure if this is a bug or if there's something wrong with my terminal settings, but when I use a command with lolcat, I get an output like this:
karenlee#Karens-MBP ~ % Documents
Downloads
Library
Movies
Music
Pictures
Postman
Public
38;5;48m
karenlee#Karens-MBP ~ % 38;5;48m
The colors look right, but as you can see, when I type the ls command on the command line, it disappears and the output also gets messy. It also seems like there's extra lines of 38;5;48m which are appearing. And it also seemed like many of the gems that are installed with lolcat have deprecated; is there another alternative to lolcat that plays nicely with macOS Catalina?
I made a shell extension for the world's fastest website generator that I make called Nift. It has an easter egg where you can turn on lolcat output for most things with lolcat.on (after starting the shell with eg. nift sh). You will even get rainbow output when pressing tab to get possible completion options, I doubt you get that with any other suggested solutions.
The shell extension is for f++ which is the in-built scripting language, which has these functions and these types available. But anything it doesn't recognise is run as a system call using the (probably primary/default) shell on your machine (hence calling it a shell extension in REPL shell mode).
Nift will look for a version of lolcat installed on your machine and otherwise use an in-built version of it which should be the world's fastest (it's near identical to my c++ implementation lolcat-cc which is the world's fastest). I highly recommend installing lolcat-cc (or another version of lolcat) on top of Nift though as otherwise you are frequently running the ~5mb Nift binary for basically all system calls, instead of a <1mb binary for lolcat.
f++ is somewhat of an interesting scripting language as well. Take this script for example which creates and deletes 100k empty text files. On my machine this runs in ~3.5 seconds whereas this Bash script doing essentially the same thing takes more like 3 minutes!! Some of the cool things you might already notice from that f++ script is you can expand variables inside strings, you can define variables with types, you can expand variables in to multiple parameters with function calls, you can have LOTS more than 10k input parameters for function calls (should be able to have millions!).
You can find some more information about the Nift REPLs (including shortcuts for different platforms) here.
If you need to define shell variables (not through f++ but the underlying shell) then you will need to do blocks of code using the sys/system function. You can also do blocks of code for Lua(JIT) and ExprTk similarly as they are both embedded in to Nift. You can use both Lua and ExprTk with accessing/modifying f++ variables as well..
So to get this shell (extension). Install Nift through a package manager or clone, make and install off GitHub. Enter nift sh in to your terminal to start the Nift f++ shell extension, then enter lolcat.on to turn on rainbow output.

External Configuration for Standalone Ruby Script

I have a standalone ruby script that is intended to be run from a commandline.
Currently I have a section to define stuff like paths to files and such.
key_path = "C:\\OpenSSL-Win64\\keys\\"
consumer_file = "henrb028.consumer"
key_file = "henrb028.key"
I'd love to pull all of that out of the .rb file and into some external config file. Ideally it would be as lightweight as possible. For example, just copy-paste that into another ruby file and then cram it in at runtime.
I've tried both require and include and gotten varying errors. Without resorting to something like YAML, what's an easy and robust way to do this?
There are a few standard ways to realize what you describe.
Read from environmental variables
Each of those environmental stores a particular parameter. Then users can control, if they want to change some or all of the parameters.
Pros: Standard and handy way for both developers and users, providing the parameters are seldom changed. The default parameters can be easily shared in the same environment (like the same account or even platform-wide), as well as with other programs.
Cons: Somewhat obscured. For example, if a user's work with the tool is logged in a file, other people (or even the user her/himself later) cannot tell what the exact set of the parameters used was from the logfile. Or, if a user wants to change a parameter frequently, it is awkward.
An example:
key_file = ENV['KEY_FILE'] || "henrb028.key"
Read from a configuration file
Then either or both of each user and the system administrator can control and change it, when needed. How to determine the filename of the configuration file varies.
Pros: Suitable to store a large set of parameters.
Cons: Somewhat obscured. For example, if a user's work with the tool is logged in a file, other people (or even the user her/himself later) cannot tell what the exact set of the parameters used was. If a user wants to change a parameter frequently, it is very awkward.
A (crude!) example:
Suppose /etc/OUR_CONFIG.txt is as follows,
CONSUMER_FILE: henrb028.consumer
KEY_FILE: henrb028.key
Then, read them like,
opts = { 'KEY_FILE' => 'default.key' }
IO.readlines("/etc/OUR_CONFIG.txt").each do |ec|
a = ec.strip.split(/\s*:\s*/)
opts[a[0]] = a[1]
end
p opts
Specify with command-line options
If some (or all) options are not specified at the run time, it should fall back to the default value. OptionParser (as #tadaman suggested in his/her comment) is a powerful and versatile library to handle command-line options.
Pros: Standard and handy way for users, especially for a quick change at the run time. If you see the log file, you know what exactly the user did.
Cons: The default parameters cannot be shared with other tools on its own. To circumvent the problem, there are a few ways to make a fall-back routine:
Simply hard-code the default value in the main code.
Store the default value in an environmental variable.
Store the default value in a configuration file. The developer must decide how to determine the filename of the configuration file (hard-coding, environmental variable, etc).
A crude example (without using OptionParser):
opts = { 'key_file' => 'default.key' }
%w(consumer_file key_file).each do |ec|
cands = ARGV.grep(/^--#{Regexp.quote ec}=.+/)
opts[ec] = cands[0].split('=')[1] unless cands.empty?
end
p opts
I think these are the most typical ways, though there are other ways!

How to determine database relations from PASE/Qshell?

I'm just stepping into the world of PASE and Qshell on the IBM i, and am still learning the "shell way" of solving problems.
I'm trying to write a shell script that will delete all logical files in a given library that are associated with a given physical file. The OS is IBM i 7.2. In QSYS-land, I'd probably DSPDBR on the physical file to an outfile, then read through the outfile and delete each dependent file. How would you do this in PASE or Qshell? I had a couple ideas, but they all seem overly-complicated, and the more I learn about shell scripting, the more shortcuts I'm finding.
My first idea was to basically replicate the above process, doing something like this and then somehow using the output of the SELECT:
system "DSPDBR FILE(MYLIB/MYFILE) OUTPUT(*OUTFILE) OUTFILE(QTEMP/DSPDBR)"
db2 "select WHREFI from QTEMP/DSPDBR where WHRELI = 'MYLIB'"
(I see now that QTEMP doesn't really work as a temporary library, but maybe there's a way around this.)
My second idea was to do something like pipe the output of the DSPDBR statement into something like awk to pick out the logical file names, and redirect the output of that to a stream file (or shell variable?). And from there, somehow use this list to delete the logical files.
Is there a more straightforward approach? It seems like whatever the answer is, it will be a pattern that is often repeated when writing shell scripts to interact with QSYS commands and objects.
First, you might review Running SQL queries from PASE instead of QSH. The db2 utility is part of Qshell and not PASE. Unfortunately, the processing behind it is based in ILE, so it's not directly usable within the PASE (AIX run-time) environment. The linked question provides a method of bridging between the two.
However, directly in QShell, you can experiment with something like this:
db2 "SELECT substr(VIEW_NAME,1,18), substr(OBJECT_NAME,1,10),
substr(OBJECT_SCHEMA,1,10), substr(VIEW_SCHEMA,1,10),
substr(TABLE_NAME,1,18)
FROM qsys2.sysviewdep
WHERE OBJECT_SCHEMA = '<yourSchemaName>'"
The SUBSTR() functions might or might not be useful. It depends on your name lengths and whether you want them limited or not. The output can be redirected to an outfile or perhaps piped into sed or another utility for manipulation.
A QTEMP library is specific to [is scoped to] the process that implements the request in the shell. Ensure all of the requests that depend on the same QTEMP library will run in the same process; the system utility runs in a separate process, as does the db2 utility. For example, in the following command-line invocation of the QSHell, all of the requests dependent on QTEMP will run in the same process in which the db2 utility runs; the db2 utility runs a script [or could instead run a dynamic compound statement] that in this scenario was generated by the scripted requests for\within the shell utility:
qsh cmd('
echo "call qsys2.qcmdexc
(''dspdbr mylib/myfile output(*outfile) outfile(qtemp/dspdbr)'')
" >> mydb2.script
; echo "select WHREFI from QTEMP.DSPDBR where WHRELI =''MYLIB''
" >> mydb2.script
; db2 -f mydb2.script
; rm mydb2.script
')
There is an open source equivalent of the QShell db2 command: https://bitbucket.org/litmis/db2util

How to keep the value of a global variable next time the script is run?

I have a VBScript file, that's called from another, which has some global variables defined, something like Dim var="whatever" outside of every function and sub. Those variables might change, maybe an instruction like var="whatever2" and their new values might be used in some calls that the other VBScript file does. But problem is that everytime the file is called their values are reset to the ones that are shown in the place where the global values are defined, so it takes the same values again.
I see two ways to solve this: One to create a text file where all actual correct values are written and then when the script is called read them from that text file, although I guess there has to be a better way. The other is to keep on using parameters for the calls, but this would likely complicate the application a lot, that's precisely the reason to use global variables, so I don't think it's a real option. I also think getting to set the values in a function wouldn't help much as they would be reseted afterwards anyway.
Any idea on how something like what I'm telling could be done?
Global variables only live as long as their process is running. If you need to persist the values of these variables so they're available when the script is run again later on you need to store them elsewhere.
The canonical methods are saving the values to a file:
Set fso = CreateObject("Scripting.FileSystemObject")
Set f = fso.OpenTextFile("C:\path\to\mystate.txt")
f.Write "var1=" & var1
f.Write "var2=" & var2
...
f.Close
or to the registry:
Set sh = CreateObject("WScript.Shell")
sh.RegWrite "HKCU\Software\mystate\var1", var1, "REG_SZ"
sh.RegWrite "HKCU\Software\mystate\var2", var2, "REG_DWORD"
...
However, if you only need them to be available during the current user session, you could also use the volatile environment:
Set sh = CreateObject("WScript.Shell")
Set env = sh.Environment("VOLATILE")
env("mystate_var1") = var1
env("mystate_var2") = var2
...
The volatile environment is only available for the current user and will vanish when the user logs off.
Consider using COM objects as your slave objects.
Script Components make vbscript COM objects. WSF script files hold many scripts in one file and can reference other files.
From Help
Windows® Script Components provide you with an easy way to create powerful, reusable COM components in script. You create script components using any scripting language that supports the Microsoft® ActiveX® Scripting interfaces. Script languages that support these interfaces include JScript, Microsoft® Visual Basic® Scripting Edition (VBScript), PERLScript, PScript, and Python.
COM Support
This new script component technology supports common types of COM components, such as Automation, and is extensible with add-ons such as DHTML behaviors.
Script Components:
Are small and efficient.
Are easy to create, maintain, and deploy.
Provide the ability to create COM components.
Provide access to a broad range of system services.
Using script components, you can create COM components for a variety of tasks, such as performing middle-tier business logic, accessing and manipulating database data, adding transaction processing to applications, and adding interactive effects to a Web page using DHTML Behaviors
and wsf files.
A Windows script (*.wsf) file is a text document containing Extensible Markup Language (XML) code. It incorporates several features that offer you increased scripting flexibility. Because Windows script files are not engine-specific, they can contain script from any Windows Script compatible scripting engine. They act as a container.
Additional Features
.wsf files support You can
Include statements
Incorporate functions from VBScript or JScript files into your Windows Script Host project.
Multiple engines
Use more than one scripting language per file.
Type libraries
Add constants to your code.
Tools
Edit files with any XML editor.
Multiple jobs in one file
Store all of your code in a single location

Resources